/*
 * SPDX-License-Identifier: MIT
 *
 * Copyright 2016 Philippe Proulx <pproulx@efficios.com>
 *
 * Babeltrace trace converter - parameter parsing
 */

#define BT_LOG_TAG "CLI/CFG-CLI-ARGS"
#include "logging.h"

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "common/assert.h"
#include <stdio.h>
#include <stdbool.h>
#include <inttypes.h>
#include <babeltrace2/babeltrace.h>
#include "common/common.h"
#include <glib.h>
#include <sys/types.h>
#include "argpar/argpar.h"
#include "babeltrace2-cfg.h"
#include "babeltrace2-cfg-cli-args.h"
#include "babeltrace2-cfg-cli-args-connect.h"
#include "param-parse/param-parse.h"
#include "babeltrace2-log-level.h"
#include "babeltrace2-plugins.h"
#include "babeltrace2-query.h"
#include "autodisc/autodisc.h"
#include "common/version.h"

#define BT_CLI_LOGE_APPEND_CAUSE_OOM() BT_CLI_LOGE_APPEND_CAUSE("Out of memory.")
#define WHILE_PARSING_ARG_N_FMT "While parsing argument #%d (`%s`): "

/*
 * Returns the plugin name, component class name, component class type,
 * and component name from a command-line --component option's argument.
 * arg must have the following format:
 *
 *     [NAME:]TYPE.PLUGIN.CLS
 *
 * where NAME is the optional component name, TYPE is either `source`,
 * `filter`, or `sink`, PLUGIN is the plugin name, and CLS is the
 * component class name.
 *
 * On success, both *plugin and *component are not NULL. *plugin
 * and *comp_cls are owned by the caller. On success, *name can be NULL
 * if no component class name was found, and *comp_cls_type is set.
 */
static
void plugin_comp_cls_names(const char *arg, char **name, char **plugin,
		char **comp_cls, bt_component_class_type *comp_cls_type)
{
	const char *at = arg;
	GString *gs_name = NULL;
	GString *gs_comp_cls_type = NULL;
	GString *gs_plugin = NULL;
	GString *gs_comp_cls = NULL;
	size_t end_pos;

	BT_ASSERT(arg);
	BT_ASSERT(plugin);
	BT_ASSERT(comp_cls);
	BT_ASSERT(comp_cls_type);

	if (!bt_common_string_is_printable(arg)) {
		BT_CLI_LOGE_APPEND_CAUSE("Argument contains a non-printable character.");
		goto error;
	}

	/* Parse the component name */
	gs_name = bt_common_string_until(at, ".:\\", ":", &end_pos);
	if (!gs_name) {
		goto error;
	}

	if (arg[end_pos] == ':') {
		at += end_pos + 1;
	} else {
		/* No name */
		g_string_assign(gs_name, "");
	}

	/* Parse the component class type */
	gs_comp_cls_type = bt_common_string_until(at, ".:\\", ".", &end_pos);
	if (!gs_comp_cls_type || at[end_pos] == '\0') {
		BT_CLI_LOGE_APPEND_CAUSE("Missing component class type (`source`, `filter`, or `sink`).");
		goto error;
	}

	if (strcmp(gs_comp_cls_type->str, "source") == 0 ||
			strcmp(gs_comp_cls_type->str, "src") == 0) {
		*comp_cls_type = BT_COMPONENT_CLASS_TYPE_SOURCE;
	} else if (strcmp(gs_comp_cls_type->str, "filter") == 0 ||
			strcmp(gs_comp_cls_type->str, "flt") == 0) {
		*comp_cls_type = BT_COMPONENT_CLASS_TYPE_FILTER;
	} else if (strcmp(gs_comp_cls_type->str, "sink") == 0) {
		*comp_cls_type = BT_COMPONENT_CLASS_TYPE_SINK;
	} else {
		BT_CLI_LOGE_APPEND_CAUSE("Unknown component class type: `%s`.",
			gs_comp_cls_type->str);
		goto error;
	}

	at += end_pos + 1;

	/* Parse the plugin name */
	gs_plugin = bt_common_string_until(at, ".:\\", ".", &end_pos);
	if (!gs_plugin || gs_plugin->len == 0 || at[end_pos] == '\0') {
		BT_CLI_LOGE_APPEND_CAUSE("Missing plugin or component class name.");
		goto error;
	}

	at += end_pos + 1;

	/* Parse the component class name */
	gs_comp_cls = bt_common_string_until(at, ".:\\", ".", &end_pos);
	if (!gs_comp_cls || gs_comp_cls->len == 0) {
		BT_CLI_LOGE_APPEND_CAUSE("Missing component class name.");
		goto error;
	}

	if (at[end_pos] != '\0') {
		/* Found a non-escaped `.` */
		goto error;
	}

	if (name) {
		if (gs_name->len == 0) {
			*name = NULL;
			g_string_free(gs_name, TRUE);
		} else {
			*name = gs_name->str;
			g_string_free(gs_name, FALSE);
		}
	} else {
		g_string_free(gs_name, TRUE);
	}

	*plugin = gs_plugin->str;
	*comp_cls = gs_comp_cls->str;
	g_string_free(gs_plugin, FALSE);
	g_string_free(gs_comp_cls, FALSE);
	gs_name = NULL;
	gs_plugin = NULL;
	gs_comp_cls = NULL;
	goto end;

error:
	if (name) {
		*name = NULL;
	}

	*plugin = NULL;
	*comp_cls = NULL;

end:
	if (gs_name) {
		g_string_free(gs_name, TRUE);
	}

	if (gs_plugin) {
		g_string_free(gs_plugin, TRUE);
	}

	if (gs_comp_cls) {
		g_string_free(gs_comp_cls, TRUE);
	}

	if (gs_comp_cls_type) {
		g_string_free(gs_comp_cls_type, TRUE);
	}

	return;
}

static
void print_and_indent(const char *str)
{
	const char *ch = &str[0];

	for (; *ch != '\0'; ch++) {
		if (*ch == '\n') {
			if (ch[1] != '\0') {
				printf("\n  ");
			}
		} else {
			printf("%c", *ch);
		}
	}

	printf("\n");
}

/*
 * Prints the Babeltrace version.
 */
static
void print_version(void)
{
	bool has_extra_name = strlen(BT_VERSION_EXTRA_NAME) > 0;
	bool has_extra_description = strlen(BT_VERSION_EXTRA_DESCRIPTION) > 0;
	bool has_extra_patch_names = strlen(BT_VERSION_EXTRA_PATCHES) > 0;
	bool has_extra = has_extra_name || has_extra_description ||
		has_extra_patch_names;

	printf("%sBabeltrace %s%s",
		bt_common_color_bold(),
		VERSION,
		bt_common_color_reset());

	if (strlen(BT_VERSION_NAME) > 0) {
		printf(" \"%s%s%s%s\"",
			bt_common_color_fg_bright_blue(),
			bt_common_color_bold(),
			BT_VERSION_NAME,
			bt_common_color_reset());
	}

	if (strlen(BT_VERSION_GIT) > 0) {
		printf(" [%s%s%s]",
			bt_common_color_fg_yellow(),
			BT_VERSION_GIT,
			bt_common_color_reset());
	}

	printf("\n");

	if (strlen(BT_VERSION_DESCRIPTION) > 0) {
		unsigned int columns;
		GString *descr;

		if (bt_common_get_term_size(&columns, NULL) < 0) {
			/* Width not found: default to 80 */
			columns = 80;
		}

		descr = bt_common_fold(BT_VERSION_DESCRIPTION, columns, 0);
		BT_ASSERT(descr);
		printf("\n%s\n", descr->str);
		g_string_free(descr, TRUE);
	}

	if (has_extra) {
		printf("\n");

		if (has_extra_name) {
			printf("%sExtra name%s: %s\n",
				bt_common_color_fg_cyan(),
				bt_common_color_reset(),
				BT_VERSION_EXTRA_NAME);
		}

		if (has_extra_description) {
			printf("%sExtra description%s:\n  ",
				bt_common_color_fg_cyan(),
				bt_common_color_reset());
			print_and_indent(BT_VERSION_EXTRA_DESCRIPTION);
		}

		if (has_extra_patch_names) {
			printf("%sExtra patch names%s:\n  ",
				bt_common_color_fg_cyan(),
				bt_common_color_reset());
			print_and_indent(BT_VERSION_EXTRA_PATCHES);
		}
	}
}

/*
 * Destroys a component configuration.
 */
static
void bt_config_component_destroy(bt_object *obj)
{
	struct bt_config_component *bt_config_component =
		container_of(obj, struct bt_config_component, base);

	if (!obj) {
		goto end;
	}

	if (bt_config_component->plugin_name) {
		g_string_free(bt_config_component->plugin_name, TRUE);
	}

	if (bt_config_component->comp_cls_name) {
		g_string_free(bt_config_component->comp_cls_name, TRUE);
	}

	if (bt_config_component->instance_name) {
		g_string_free(bt_config_component->instance_name, TRUE);
	}

	BT_VALUE_PUT_REF_AND_RESET(bt_config_component->params);
	g_free(bt_config_component);

end:
	return;
}

/*
 * Creates a component configuration using the given plugin name and
 * component name. `plugin_name` and `comp_cls_name` are copied (belong
 * to the return value).
 *
 * Return value is owned by the caller.
 */
static
struct bt_config_component *bt_config_component_create(
		bt_component_class_type type,
		const char *plugin_name, const char *comp_cls_name,
		int init_log_level)
{
	struct bt_config_component *cfg_component = NULL;

	cfg_component = g_new0(struct bt_config_component, 1);
	if (!cfg_component) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	bt_object_init_shared(&cfg_component->base,
		bt_config_component_destroy);
	cfg_component->type = type;
	cfg_component->plugin_name = g_string_new(plugin_name);
	if (!cfg_component->plugin_name) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	cfg_component->comp_cls_name = g_string_new(comp_cls_name);
	if (!cfg_component->comp_cls_name) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	cfg_component->instance_name = g_string_new(NULL);
	if (!cfg_component->instance_name) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	cfg_component->log_level = init_log_level;

	/* Start with empty parameters */
	cfg_component->params = bt_value_map_create();
	if (!cfg_component->params) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	goto end;

error:
	BT_OBJECT_PUT_REF_AND_RESET(cfg_component);

end:
	return cfg_component;
}

/*
 * Creates a component configuration from a command-line --component
 * option's argument.
 */
static
struct bt_config_component *bt_config_component_from_arg(const char *arg,
		int init_log_level)
{
	struct bt_config_component *cfg_comp = NULL;
	char *name = NULL;
	char *plugin_name = NULL;
	char *comp_cls_name = NULL;
	bt_component_class_type type;

	plugin_comp_cls_names(arg, &name, &plugin_name, &comp_cls_name, &type);
	if (!plugin_name || !comp_cls_name) {
		goto error;
	}

	cfg_comp = bt_config_component_create(type, plugin_name, comp_cls_name,
		init_log_level);
	if (!cfg_comp) {
		goto error;
	}

	if (name) {
		g_string_assign(cfg_comp->instance_name, name);
	}

	goto end;

error:
	BT_OBJECT_PUT_REF_AND_RESET(cfg_comp);

end:
	g_free(name);
	g_free(plugin_name);
	g_free(comp_cls_name);
	return cfg_comp;
}

/*
 * Destroys a configuration.
 */
static
void bt_config_destroy(bt_object *obj)
{
	struct bt_config *cfg =
		container_of(obj, struct bt_config, base);

	if (!obj) {
		goto end;
	}

	BT_VALUE_PUT_REF_AND_RESET(cfg->plugin_paths);

	switch (cfg->command) {
	case BT_CONFIG_COMMAND_RUN:
		if (cfg->cmd_data.run.sources) {
			g_ptr_array_free(cfg->cmd_data.run.sources, TRUE);
		}

		if (cfg->cmd_data.run.filters) {
			g_ptr_array_free(cfg->cmd_data.run.filters, TRUE);
		}

		if (cfg->cmd_data.run.sinks) {
			g_ptr_array_free(cfg->cmd_data.run.sinks, TRUE);
		}

		if (cfg->cmd_data.run.connections) {
			g_ptr_array_free(cfg->cmd_data.run.connections,
				TRUE);
		}
		break;
	case BT_CONFIG_COMMAND_LIST_PLUGINS:
		break;
	case BT_CONFIG_COMMAND_HELP:
		BT_OBJECT_PUT_REF_AND_RESET(cfg->cmd_data.help.cfg_component);
		break;
	case BT_CONFIG_COMMAND_QUERY:
		BT_OBJECT_PUT_REF_AND_RESET(cfg->cmd_data.query.cfg_component);

		if (cfg->cmd_data.query.object) {
			g_string_free(cfg->cmd_data.query.object, TRUE);
		}
		break;
	case BT_CONFIG_COMMAND_PRINT_CTF_METADATA:
		if (cfg->cmd_data.print_ctf_metadata.path) {
			g_string_free(cfg->cmd_data.print_ctf_metadata.path,
				TRUE);
			g_string_free(
				cfg->cmd_data.print_ctf_metadata.output_path,
				TRUE);
		}
		break;
	case BT_CONFIG_COMMAND_PRINT_LTTNG_LIVE_SESSIONS:
		if (cfg->cmd_data.print_lttng_live_sessions.url) {
			g_string_free(
				cfg->cmd_data.print_lttng_live_sessions.url,
				TRUE);
			g_string_free(
				cfg->cmd_data.print_lttng_live_sessions.output_path,
				TRUE);
		}
		break;
	default:
		bt_common_abort();
	}

	g_free(cfg);

end:
	return;
}

static
void destroy_glist_of_gstring(GList *list)
{
	GList *at;

	if (!list) {
		return;
	}

	for (at = list; at; at = g_list_next(at)) {
		g_string_free(at->data, TRUE);
	}

	g_list_free(list);
}

/*
 * Creates a simple lexical scanner for parsing comma-delimited names
 * and fields.
 *
 * Return value is owned by the caller.
 */
static
GScanner *create_csv_identifiers_scanner(void)
{
	GScanner *scanner;
	GScannerConfig scanner_config = {
		.cset_skip_characters = (gchar *) " \t\n",
		.cset_identifier_first = (gchar *) G_CSET_a_2_z G_CSET_A_2_Z "_",
		.cset_identifier_nth = (gchar *) G_CSET_a_2_z G_CSET_A_2_Z ":_-",
		.case_sensitive = TRUE,
		.cpair_comment_single = NULL,
		.skip_comment_multi = TRUE,
		.skip_comment_single = TRUE,
		.scan_comment_multi = FALSE,
		.scan_identifier = TRUE,
		.scan_identifier_1char = TRUE,
		.scan_identifier_NULL = FALSE,
		.scan_symbols = FALSE,
		.symbol_2_token = FALSE,
		.scope_0_fallback = FALSE,
		.scan_binary = FALSE,
		.scan_octal = FALSE,
		.scan_float = FALSE,
		.scan_hex = FALSE,
		.scan_hex_dollar = FALSE,
		.numbers_2_int = FALSE,
		.int_2_float = FALSE,
		.store_int64 = FALSE,
		.scan_string_sq = FALSE,
		.scan_string_dq = FALSE,
		.identifier_2_string = FALSE,
		.char_2_token = TRUE,
	};

	scanner = g_scanner_new(&scanner_config);
	if (!scanner) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
	}

	return scanner;
}

/*
 * Converts a comma-delimited list of known names (--names option) to
 * an array value object containing those names as string value objects.
 *
 * Return value is owned by the caller.
 */
static
bt_value *names_from_arg(const char *arg)
{
	GScanner *scanner = NULL;
	bt_value *names = NULL;
	bool found_all = false, found_none = false, found_item = false;

	names = bt_value_array_create();
	if (!names) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	scanner = create_csv_identifiers_scanner();
	if (!scanner) {
		goto error;
	}

	g_scanner_input_text(scanner, arg, strlen(arg));

	while (true) {
		GTokenType token_type = g_scanner_get_next_token(scanner);

		switch (token_type) {
		case G_TOKEN_IDENTIFIER:
		{
			const char *identifier = scanner->value.v_identifier;

			if (strcmp(identifier, "payload") == 0 ||
					strcmp(identifier, "args") == 0 ||
					strcmp(identifier, "arg") == 0) {
				found_item = true;
				if (bt_value_array_append_string_element(names,
						"payload")) {
					goto error;
				}
			} else if (strcmp(identifier, "context") == 0 ||
					strcmp(identifier, "ctx") == 0) {
				found_item = true;
				if (bt_value_array_append_string_element(names,
						"context")) {
					goto error;
				}
			} else if (strcmp(identifier, "scope") == 0 ||
					strcmp(identifier, "header") == 0) {
				found_item = true;
				if (bt_value_array_append_string_element(names,
						identifier)) {
					goto error;
				}
			} else if (strcmp(identifier, "all") == 0) {
				found_all = true;
				if (bt_value_array_append_string_element(names,
						identifier)) {
					goto error;
				}
			} else if (strcmp(identifier, "none") == 0) {
				found_none = true;
				if (bt_value_array_append_string_element(names,
						identifier)) {
					goto error;
				}
			} else {
				BT_CLI_LOGE_APPEND_CAUSE("Unknown name: `%s`.",
					identifier);
				goto error;
			}
			break;
		}
		case G_TOKEN_COMMA:
			continue;
		case G_TOKEN_EOF:
			goto end;
		default:
			goto error;
		}
	}

end:
	if (found_none && found_all) {
		BT_CLI_LOGE_APPEND_CAUSE("Only either `all` or `none` can be specified in the list given to the --names option, but not both.");
		goto error;
	}
	/*
	 * Legacy behavior is to clear the defaults (show none) when at
	 * least one item is specified.
	 */
	if (found_item && !found_none && !found_all) {
		if (bt_value_array_append_string_element(names, "none")) {
			goto error;
		}
	}
	if (scanner) {
		g_scanner_destroy(scanner);
	}
	return names;

error:
	BT_VALUE_PUT_REF_AND_RESET(names);
	if (scanner) {
		g_scanner_destroy(scanner);
	}
	return names;
}

/*
 * Converts a comma-delimited list of known fields (--fields option) to
 * an array value object containing those fields as string
 * value objects.
 *
 * Return value is owned by the caller.
 */
static
bt_value *fields_from_arg(const char *arg)
{
	GScanner *scanner = NULL;
	bt_value *fields;

	fields = bt_value_array_create();
	if (!fields) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	scanner = create_csv_identifiers_scanner();
	if (!scanner) {
		goto error;
	}

	g_scanner_input_text(scanner, arg, strlen(arg));

	while (true) {
		GTokenType token_type = g_scanner_get_next_token(scanner);

		switch (token_type) {
		case G_TOKEN_IDENTIFIER:
		{
			const char *identifier = scanner->value.v_identifier;

			if (strcmp(identifier, "trace") == 0 ||
					strcmp(identifier, "trace:hostname") == 0 ||
					strcmp(identifier, "trace:domain") == 0 ||
					strcmp(identifier, "trace:procname") == 0 ||
					strcmp(identifier, "trace:vpid") == 0 ||
					strcmp(identifier, "loglevel") == 0 ||
					strcmp(identifier, "emf") == 0 ||
					strcmp(identifier, "callsite") == 0 ||
					strcmp(identifier, "all") == 0) {
				if (bt_value_array_append_string_element(fields,
						identifier)) {
					goto error;
				}
			} else {
				BT_CLI_LOGE_APPEND_CAUSE("Unknown field: `%s`.",
					identifier);
				goto error;
			}
			break;
		}
		case G_TOKEN_COMMA:
			continue;
		case G_TOKEN_EOF:
			goto end;
		default:
			goto error;
		}
	}

	goto end;

error:
	BT_VALUE_PUT_REF_AND_RESET(fields);

end:
	if (scanner) {
		g_scanner_destroy(scanner);
	}
	return fields;
}

static
void append_param_arg(GString *params_arg, const char *key, const char *value)
{
	BT_ASSERT(params_arg);
	BT_ASSERT(key);
	BT_ASSERT(value);

	if (params_arg->len != 0) {
		g_string_append_c(params_arg, ',');
	}

	g_string_append(params_arg, key);
	g_string_append_c(params_arg, '=');
	g_string_append(params_arg, value);
}

/*
 * Inserts the equivalent "prefix-NAME=yes" strings into params_arg
 * where the names are in names_array.
 */
static
int insert_flat_params_from_array(GString *params_arg,
		const bt_value *names_array, const char *prefix)
{
	int ret = 0;
	uint64_t i;
	GString *tmpstr = NULL, *default_value = NULL;
	bool default_set = false, non_default_set = false;

	/*
	 * names_array may be NULL if no CLI options were specified to
	 * trigger its creation.
	 */
	if (!names_array) {
		goto end;
	}

	tmpstr = g_string_new(NULL);
	if (!tmpstr) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		ret = -1;
		goto end;
	}

	default_value = g_string_new(NULL);
	if (!default_value) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		ret = -1;
		goto end;
	}

	for (i = 0; i < bt_value_array_get_length(names_array); i++) {
		const bt_value *str_obj =
			bt_value_array_borrow_element_by_index_const(names_array,
				i);
		const char *suffix;
		bool is_default = false;

		suffix = bt_value_string_get(str_obj);

		g_string_assign(tmpstr, prefix);
		g_string_append(tmpstr, "-");

		/* Special-case for "all" and "none". */
		if (strcmp(suffix, "all") == 0) {
			is_default = true;
			g_string_assign(default_value, "show");
		} else if (strcmp(suffix, "none") == 0) {
			is_default = true;
			g_string_assign(default_value, "hide");
		}
		if (is_default) {
			default_set = true;
			g_string_append(tmpstr, "default");
			append_param_arg(params_arg, tmpstr->str,
				default_value->str);
		} else {
			non_default_set = true;
			g_string_append(tmpstr, suffix);
			append_param_arg(params_arg, tmpstr->str, "yes");
		}
	}

	/* Implicit field-default=hide if any non-default option is set. */
	if (non_default_set && !default_set) {
		g_string_assign(tmpstr, prefix);
		g_string_append(tmpstr, "-default");
		g_string_assign(default_value, "hide");
		append_param_arg(params_arg, tmpstr->str, default_value->str);
	}

end:
	if (default_value) {
		g_string_free(default_value, TRUE);
	}

	if (tmpstr) {
		g_string_free(tmpstr, TRUE);
	}

	return ret;
}

/* argpar options */
enum {
	OPT_NONE = 0,
	OPT_BASE_PARAMS,
	OPT_BEGIN,
	OPT_CLOCK_CYCLES,
	OPT_CLOCK_DATE,
	OPT_CLOCK_FORCE_CORRELATE,
	OPT_CLOCK_GMT,
	OPT_CLOCK_OFFSET,
	OPT_CLOCK_OFFSET_NS,
	OPT_CLOCK_SECONDS,
	OPT_COLOR,
	OPT_COMPONENT,
	OPT_CONNECT,
	OPT_DEBUG,
	OPT_DEBUG_INFO,
	OPT_DEBUG_INFO_DIR,
	OPT_DEBUG_INFO_FULL_PATH,
	OPT_DEBUG_INFO_TARGET_PREFIX,
	OPT_END,
	OPT_FIELDS,
	OPT_HELP,
	OPT_INPUT_FORMAT,
	OPT_LIST,
	OPT_LOG_LEVEL,
	OPT_NAMES,
	OPT_NO_DELTA,
	OPT_OMIT_HOME_PLUGIN_PATH,
	OPT_OMIT_SYSTEM_PLUGIN_PATH,
	OPT_OUTPUT,
	OPT_OUTPUT_FORMAT,
	OPT_PARAMS,
	OPT_PLUGIN_PATH,
	OPT_RESET_BASE_PARAMS,
	OPT_RETRY_DURATION,
	OPT_RUN_ARGS,
	OPT_RUN_ARGS_0,
	OPT_STREAM_INTERSECTION,
	OPT_TIMERANGE,
	OPT_VERBOSE,
	OPT_VERSION,
};

enum bt_config_component_dest {
	BT_CONFIG_COMPONENT_DEST_UNKNOWN = -1,
	BT_CONFIG_COMPONENT_DEST_SOURCE,
	BT_CONFIG_COMPONENT_DEST_FILTER,
	BT_CONFIG_COMPONENT_DEST_SINK,
};

/*
 * Adds a configuration component to the appropriate configuration
 * array depending on the destination.
 */
static
void add_run_cfg_comp(struct bt_config *cfg,
		struct bt_config_component *cfg_comp,
		enum bt_config_component_dest dest)
{
	bt_object_get_ref(cfg_comp);

	switch (dest) {
	case BT_CONFIG_COMPONENT_DEST_SOURCE:
		g_ptr_array_add(cfg->cmd_data.run.sources, cfg_comp);
		break;
	case BT_CONFIG_COMPONENT_DEST_FILTER:
		g_ptr_array_add(cfg->cmd_data.run.filters, cfg_comp);
		break;
	case BT_CONFIG_COMPONENT_DEST_SINK:
		g_ptr_array_add(cfg->cmd_data.run.sinks, cfg_comp);
		break;
	default:
		bt_common_abort();
	}
}

static
int add_run_cfg_comp_check_name(struct bt_config *cfg,
		struct bt_config_component *cfg_comp,
		enum bt_config_component_dest dest,
		bt_value *instance_names)
{
	int ret = 0;

	if (cfg_comp->instance_name->len == 0) {
		BT_CLI_LOGE_APPEND_CAUSE("Found an unnamed component.");
		ret = -1;
		goto end;
	}

	if (bt_value_map_has_entry(instance_names,
				   cfg_comp->instance_name->str)) {
		BT_CLI_LOGE_APPEND_CAUSE("Duplicate component instance name:\n    %s",
			cfg_comp->instance_name->str);
		ret = -1;
		goto end;
	}

	if (bt_value_map_insert_entry(instance_names,
			cfg_comp->instance_name->str, bt_value_null)) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		ret = -1;
		goto end;
	}

	add_run_cfg_comp(cfg, cfg_comp, dest);

end:
	return ret;
}

static
int append_env_var_plugin_paths(bt_value *plugin_paths)
{
	int ret = 0;
	const char *envvar;

	if (bt_common_is_setuid_setgid()) {
		BT_LOGI_STR("Skipping non-system plugin paths for setuid/setgid binary.");
		goto end;
	}

	envvar = getenv("BABELTRACE_PLUGIN_PATH");
	if (!envvar) {
		goto end;
	}

	ret = bt_config_append_plugin_paths(plugin_paths, envvar);

end:
	if (ret) {
		BT_CLI_LOGE_APPEND_CAUSE("Cannot append plugin paths from BABELTRACE_PLUGIN_PATH.");
	}

	return ret;
}

static
int append_home_and_system_plugin_paths(bt_value *plugin_paths,
		bool omit_system_plugin_path, bool omit_home_plugin_path)
{
	int ret;

	if (!omit_home_plugin_path) {
		if (bt_common_is_setuid_setgid()) {
			BT_LOGI_STR("Skipping non-system plugin paths for setuid/setgid binary.");
		} else {
			char *home_plugin_dir = bt_common_get_home_plugin_path(
				BT_LOG_OUTPUT_LEVEL);

			if (home_plugin_dir) {
				ret = bt_config_append_plugin_paths(
					plugin_paths, home_plugin_dir);
				free(home_plugin_dir);

				if (ret) {
					BT_CLI_LOGE_APPEND_CAUSE("Invalid home plugin path.");
					goto error;
				}
			}
		}
	}

	if (!omit_system_plugin_path) {
		if (bt_config_append_plugin_paths(plugin_paths,
				bt_common_get_system_plugin_path())) {
			BT_CLI_LOGE_APPEND_CAUSE("Invalid system plugin path.");
			goto error;
		}
	}
	return 0;
error:
	BT_CLI_LOGE_APPEND_CAUSE("Cannot append home and system plugin paths.");
	return -1;
}

static
struct bt_config *bt_config_base_create(enum bt_config_command command,
		const bt_value *plugin_paths, bool needs_plugins)
{
	struct bt_config *cfg;

	/* Create config */
	cfg = g_new0(struct bt_config, 1);
	if (!cfg) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	bt_object_init_shared(&cfg->base, bt_config_destroy);
	cfg->command = command;
	cfg->command_needs_plugins = needs_plugins;

	if (plugin_paths) {
		bt_value *plugin_paths_copy;

		(void) bt_value_copy(plugin_paths,
			&plugin_paths_copy);
		cfg->plugin_paths = plugin_paths_copy;
	} else {
		cfg->plugin_paths = bt_value_array_create();
		if (!cfg->plugin_paths) {
			BT_CLI_LOGE_APPEND_CAUSE_OOM();
			goto error;
		}
	}

	goto end;

error:
	BT_OBJECT_PUT_REF_AND_RESET(cfg);

end:
	return cfg;
}

static
struct bt_config *bt_config_run_create(const bt_value *plugin_paths)
{
	struct bt_config *cfg;

	/* Create config */
	cfg = bt_config_base_create(BT_CONFIG_COMMAND_RUN,
		plugin_paths, true);
	if (!cfg) {
		goto error;
	}

	cfg->cmd_data.run.sources = g_ptr_array_new_with_free_func(
		(GDestroyNotify) bt_object_put_ref);
	if (!cfg->cmd_data.run.sources) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	cfg->cmd_data.run.filters = g_ptr_array_new_with_free_func(
		(GDestroyNotify) bt_object_put_ref);
	if (!cfg->cmd_data.run.filters) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	cfg->cmd_data.run.sinks = g_ptr_array_new_with_free_func(
		(GDestroyNotify) bt_object_put_ref);
	if (!cfg->cmd_data.run.sinks) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	cfg->cmd_data.run.connections = g_ptr_array_new_with_free_func(
		(GDestroyNotify) bt_config_connection_destroy);
	if (!cfg->cmd_data.run.connections) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	goto end;

error:
	BT_OBJECT_PUT_REF_AND_RESET(cfg);

end:
	return cfg;
}

static
struct bt_config *bt_config_list_plugins_create(const bt_value *plugin_paths)
{
	return bt_config_base_create(BT_CONFIG_COMMAND_LIST_PLUGINS,
		plugin_paths, true);
}

static
struct bt_config *bt_config_help_create(const bt_value *plugin_paths,
		int default_log_level)
{
	struct bt_config *cfg;

	/* Create config */
	cfg = bt_config_base_create(BT_CONFIG_COMMAND_HELP,
		plugin_paths, true);
	if (!cfg) {
		goto error;
	}

	cfg->cmd_data.help.cfg_component =
		bt_config_component_create(-1, NULL, NULL, default_log_level);
	if (!cfg->cmd_data.help.cfg_component) {
		goto error;
	}

	goto end;

error:
	BT_OBJECT_PUT_REF_AND_RESET(cfg);

end:
	return cfg;
}

static
struct bt_config *bt_config_query_create(const bt_value *plugin_paths)
{
	struct bt_config *cfg;

	/* Create config */
	cfg = bt_config_base_create(BT_CONFIG_COMMAND_QUERY,
		plugin_paths, true);
	if (!cfg) {
		goto error;
	}

	cfg->cmd_data.query.object = g_string_new(NULL);
	if (!cfg->cmd_data.query.object) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	goto end;

error:
	BT_OBJECT_PUT_REF_AND_RESET(cfg);

end:
	return cfg;
}

static
enum bt_config_cli_args_status bt_config_print_ctf_metadata_create(
		const bt_value *plugin_paths, struct bt_config **cfg_out)
{
	struct bt_config *cfg;
	enum bt_config_cli_args_status status;

	/* Create config */
	cfg = bt_config_base_create(BT_CONFIG_COMMAND_PRINT_CTF_METADATA,
		plugin_paths, true);
	if (!cfg) {
		goto error;
	}

	cfg->cmd_data.print_ctf_metadata.path = g_string_new(NULL);
	if (!cfg->cmd_data.print_ctf_metadata.path) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	cfg->cmd_data.print_ctf_metadata.output_path = g_string_new(NULL);
	if (!cfg->cmd_data.print_ctf_metadata.output_path) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	BT_OBJECT_MOVE_REF(*cfg_out, cfg);
	status = BT_CONFIG_CLI_ARGS_STATUS_OK;
	goto end;

error:
	status = BT_CONFIG_CLI_ARGS_STATUS_ERROR;

end:
	bt_object_put_ref(cfg);
	return status;
}

static
enum bt_config_cli_args_status bt_config_print_lttng_live_sessions_create(
		const bt_value *plugin_paths, struct bt_config **cfg_out)
{
	struct bt_config *cfg;
	enum bt_config_cli_args_status status;

	/* Create config */
	cfg = bt_config_base_create(BT_CONFIG_COMMAND_PRINT_LTTNG_LIVE_SESSIONS,
		plugin_paths, true);
	if (!cfg) {
		goto error;
	}

	cfg->cmd_data.print_lttng_live_sessions.url = g_string_new(NULL);
	if (!cfg->cmd_data.print_lttng_live_sessions.url) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	cfg->cmd_data.print_lttng_live_sessions.output_path =
		g_string_new(NULL);
	if (!cfg->cmd_data.print_lttng_live_sessions.output_path) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	BT_OBJECT_MOVE_REF(*cfg_out, cfg);
	status = BT_CONFIG_CLI_ARGS_STATUS_OK;
	goto end;

error:
	status = BT_CONFIG_CLI_ARGS_STATUS_ERROR;

end:
	bt_object_put_ref(cfg);
	return status;
}

static
int bt_config_append_plugin_paths_check_setuid_setgid(
		bt_value *plugin_paths, const char *arg)
{
	int ret = 0;

	if (bt_common_is_setuid_setgid()) {
		BT_LOGI_STR("Skipping non-system plugin paths for setuid/setgid binary.");
		goto end;
	}

	if (bt_config_append_plugin_paths(plugin_paths, arg)) {
		BT_CLI_LOGE_APPEND_CAUSE("Invalid --plugin-path option's argument:\n    %s",
			arg);
		ret = -1;
		goto end;
	}

end:
	return ret;
}

/*
 * Prints the expected format for a --params option.
 */
static
void print_expected_params_format(FILE *fp)
{
	fprintf(fp, "Expected format of PARAMS\n");
	fprintf(fp, "-------------------------\n");
	fprintf(fp, "\n");
	fprintf(fp, "    PARAM=VALUE[,PARAM=VALUE]...\n");
	fprintf(fp, "\n");
	fprintf(fp, "The parameter string is a comma-separated list of PARAM=VALUE assignments,\n");
	fprintf(fp, "where PARAM is the parameter name (C identifier plus the [:.-] characters),\n");
	fprintf(fp, "and VALUE can be one of:\n");
	fprintf(fp, "\n");
	fprintf(fp, "* `null`, `nul`, `NULL`: null value (no backticks).\n");
	fprintf(fp, "* `true`, `TRUE`, `yes`, `YES`: true boolean value (no backticks).\n");
	fprintf(fp, "* `false`, `FALSE`, `no`, `NO`: false boolean value (no backticks).\n");
	fprintf(fp, "* Binary (`0b` prefix), octal (`0` prefix), decimal, or hexadecimal\n");
	fprintf(fp, "  (`0x` prefix) unsigned (with `+` prefix) or signed 64-bit integer.\n");
	fprintf(fp, "* Double precision floating point number (scientific notation is accepted).\n");
	fprintf(fp, "* Unquoted string with no special characters, and not matching any of\n");
	fprintf(fp, "  the null and boolean value symbols above.\n");
	fprintf(fp, "* Double-quoted string (accepts escape characters).\n");
	fprintf(fp, "* Array, formatted as an opening `[`, a list of comma-separated values\n");
	fprintf(fp, "  (as described by the current list) and a closing `]`.\n");
	fprintf(fp, "* Map, formatted as an opening `{`, a comma-separated list of PARAM=VALUE\n");
	fprintf(fp, "  assignments and a closing `}`.\n");
	fprintf(fp, "\n");
	fprintf(fp, "You can put whitespaces allowed around individual `=` and `,` symbols.\n");
	fprintf(fp, "\n");
	fprintf(fp, "Example:\n");
	fprintf(fp, "\n");
	fprintf(fp, "    many=null, fresh=yes, condition=false, squirrel=-782329,\n");
	fprintf(fp, "    play=+23, observe=3.14, simple=beef, needs-quotes=\"some string\",\n");
	fprintf(fp, "    escape.chars-are:allowed=\"this is a \\\" double quote\",\n");
	fprintf(fp, "    things=[1, \"2\", 3]\n");
	fprintf(fp, "\n");
	fprintf(fp, "IMPORTANT: Make sure to single-quote the whole argument when you run\n");
	fprintf(fp, "babeltrace2 from a shell.\n");
}

/*
 * Given argpar error status `status` and error `error`, return a formatted
 * error message describing the error.
 *
 * `argv` is the argument vector that was being parsed.
 *
 * `prefix_fmt` (formatted using the following arguments) is prepended to
 * the error message.
 *
 * The returned string must be freed by the caller.
 */
static
GString *__BT_ATTR_FORMAT_PRINTF(4, 5) format_arg_error(
		const struct argpar_error *error,
		const char **argv, unsigned int arg_index_offset,
		const char *prefix_fmt, ...)
{
	GString *str = g_string_new(NULL);
	va_list args;

	va_start(args, prefix_fmt);
	g_string_append_vprintf(str, prefix_fmt, args);
	va_end(args);

	g_string_append(str, ": ");

	switch (argpar_error_type(error))
	{
	case ARGPAR_ERROR_TYPE_MISSING_OPT_ARG:
	{
		bool is_short;
		const struct argpar_opt_descr *descr =
			argpar_error_opt_descr(error, &is_short);
		int orig_index = argpar_error_orig_index(error);
		const char *arg = argv[orig_index];

		if (is_short) {
			g_string_append_printf(
				str,
				WHILE_PARSING_ARG_N_FMT "Missing required argument for option `-%c`",
				arg_index_offset + orig_index + 1, arg, descr->short_name);
		} else {
			g_string_append_printf(
				str,
				WHILE_PARSING_ARG_N_FMT "Missing required argument for option `--%s`",
				arg_index_offset + orig_index + 1, arg, descr->long_name);
		}

		break;
	}
	case ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG:
	{
		bool is_short;
		const struct argpar_opt_descr *descr =
			argpar_error_opt_descr(error, &is_short);
		int orig_index = argpar_error_orig_index(error);
		const char *arg = argv[orig_index];

		if (is_short) {
			g_string_append_printf(
				str,
				WHILE_PARSING_ARG_N_FMT "Unexpected argument for option `-%c`",
				arg_index_offset + orig_index + 1, arg, descr->short_name);
		} else {
			g_string_append_printf(
				str,
				WHILE_PARSING_ARG_N_FMT "Unexpected argument for option `--%s`",
				arg_index_offset + orig_index + 1, arg, descr->long_name);
		}

		break;
	}
	case ARGPAR_ERROR_TYPE_UNKNOWN_OPT:
	{
		int orig_index = argpar_error_orig_index(error);
		const char *unknown_opt = argpar_error_unknown_opt_name(error);
		const char *arg = argv[orig_index];

		g_string_append_printf(
			str,
			WHILE_PARSING_ARG_N_FMT "Unknown option `%s`",
			arg_index_offset + orig_index + 1, arg, unknown_opt);

		break;
	}

	default:
		BT_ASSERT(0);
	}

	return str;
}

enum parse_next_item_status
{
	PARSE_NEXT_ITEM_STATUS_OK = 0,
	PARSE_NEXT_ITEM_STATUS_END = 1,
	PARSE_NEXT_ITEM_STATUS_ERROR = -1,
};

/*
 * Parse the next item using `iter`.  Log and append an error if necessary.
 *
 * The item in `*item` on entry is freed, and the new item is also
 * returned in `*item`.
 */
static
enum parse_next_item_status parse_next_item(struct argpar_iter *iter,
		const struct argpar_item **item, const char **argv,
		const char *command, unsigned int consumed_args)
{
	enum argpar_iter_next_status status;
	const struct argpar_error *error = NULL;
	enum parse_next_item_status ret;

	ARGPAR_ITEM_DESTROY_AND_RESET(*item);
	status = argpar_iter_next(iter, item, &error);

	switch (status) {
	case ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY:
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		ret = PARSE_NEXT_ITEM_STATUS_ERROR;
		break;
	case ARGPAR_ITER_NEXT_STATUS_ERROR:
	{
		GString *err_str = format_arg_error(error, argv,
			consumed_args,
			"While parsing `%s` command's command-line arguments",
			command);
		BT_CLI_LOGE_APPEND_CAUSE("%s", err_str->str);
		g_string_free(err_str, TRUE);
		ret = PARSE_NEXT_ITEM_STATUS_ERROR;
		break;
	}
	case ARGPAR_ITER_NEXT_STATUS_END:
		ret = PARSE_NEXT_ITEM_STATUS_END;
		break;
	case ARGPAR_ITER_NEXT_STATUS_OK:
		ret = PARSE_NEXT_ITEM_STATUS_OK;
		break;
	default:
		bt_common_abort();
	}

	argpar_error_destroy(error);
	return ret;
}


/*
 * Prints the help command usage.
 */
static
void print_help_usage(FILE *fp)
{
	fprintf(fp, "Usage: babeltrace2 [GENERAL OPTIONS] help [OPTIONS] PLUGIN\n");
	fprintf(fp, "       babeltrace2 [GENERAL OPTIONS] help [OPTIONS] TYPE.PLUGIN.CLS\n");
	fprintf(fp, "\n");
	fprintf(fp, "Options:\n");
	fprintf(fp, "\n");
	fprintf(fp, "  -h, --help  Show this help and quit\n");
	fprintf(fp, "\n");
	fprintf(fp, "See `babeltrace2 --help` for the list of general options.\n");
	fprintf(fp, "\n");
	fprintf(fp, "Use `babeltrace2 list-plugins` to show the list of available plugins.\n");
}

static
const struct argpar_opt_descr help_options[] = {
	/* id, short_name, long_name, with_arg */
	{ OPT_HELP, 'h', "help", false },
	ARGPAR_OPT_DESCR_SENTINEL
};

/*
 * Creates a Babeltrace config object from the arguments of a help
 * command.
 */
static
enum bt_config_cli_args_status bt_config_help_from_args(int argc,
		const char *argv[], struct bt_config **cfg_out,
		const bt_value *plugin_paths, int default_log_level,
		unsigned int consumed_args)
{
	enum bt_config_cli_args_status status;
	struct bt_config *cfg;
	const char *plugin_comp_cls_arg = NULL;
	char *plugin_name = NULL, *comp_cls_name = NULL;
	GString *substring = NULL;
	size_t end_pos;
	struct argpar_iter *argpar_iter = NULL;
	const struct argpar_item *argpar_item = NULL;

	cfg = bt_config_help_create(plugin_paths, default_log_level);
	if (!cfg) {
		goto error;
	}

	argpar_iter = argpar_iter_create(argc, argv, help_options);
	if (!argpar_iter) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	while (true) {
		enum parse_next_item_status parse_status =
			parse_next_item(argpar_iter, &argpar_item, argv, "help",
				consumed_args);

		if (parse_status == PARSE_NEXT_ITEM_STATUS_ERROR) {
			goto error;
		} else if (parse_status == PARSE_NEXT_ITEM_STATUS_END) {
			break;
		}

		if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) {
			const struct argpar_opt_descr *opt_descr =
				argpar_item_opt_descr(argpar_item);

			switch (opt_descr->id) {
			case OPT_HELP:
				print_help_usage(stdout);
				status = BT_CONFIG_CLI_ARGS_STATUS_INFO_ONLY;
				goto end;
			default:
				bt_common_abort();
			}
		} else {
			const char *arg = argpar_item_non_opt_arg(argpar_item);

			if (plugin_comp_cls_arg) {
				BT_CLI_LOGE_APPEND_CAUSE(
					"Extraneous command-line argument specified to `help` command: `%s`.",
					arg);
				goto error;
			}

			plugin_comp_cls_arg = arg;
		}
	}

	if (!plugin_comp_cls_arg) {
		BT_CLI_LOGE_APPEND_CAUSE(
			"Missing plugin name or component class descriptor.");
		goto error;
	}

	/* Look for unescaped dots in the argument. */
	substring = bt_common_string_until(plugin_comp_cls_arg, ".\\", ".", &end_pos);
	if (!substring) {
		BT_CLI_LOGE_APPEND_CAUSE("Could not consume argument: arg=%s",
			plugin_comp_cls_arg);
		goto error;
	}

	if (end_pos == strlen(plugin_comp_cls_arg)) {
		/* Didn't find an unescaped dot, treat it as a plugin name. */
		g_string_assign(cfg->cmd_data.help.cfg_component->plugin_name,
			plugin_comp_cls_arg);
	} else {
		/*
		 * Found an unescaped dot, treat it as a component class name.
		 */
		plugin_comp_cls_names(plugin_comp_cls_arg, NULL, &plugin_name, &comp_cls_name,
			&cfg->cmd_data.help.cfg_component->type);
		if (!plugin_name || !comp_cls_name) {
			BT_CLI_LOGE_APPEND_CAUSE(
				"Could not parse argument as a component class name: arg=%s",
				plugin_comp_cls_arg);
			goto error;
		}

		g_string_assign(cfg->cmd_data.help.cfg_component->plugin_name,
			plugin_name);
		g_string_assign(cfg->cmd_data.help.cfg_component->comp_cls_name,
			comp_cls_name);
	}

	BT_OBJECT_MOVE_REF(*cfg_out, cfg);
	status = BT_CONFIG_CLI_ARGS_STATUS_OK;
	goto end;

error:
	status = BT_CONFIG_CLI_ARGS_STATUS_ERROR;

end:
	g_free(plugin_name);
	g_free(comp_cls_name);

	if (substring) {
		g_string_free(substring, TRUE);
	}

	argpar_iter_destroy(argpar_iter);
	argpar_item_destroy(argpar_item);
	bt_object_put_ref(cfg);

	return status;
}

/*
 * Prints the help command usage.
 */
static
void print_query_usage(FILE *fp)
{
	fprintf(fp, "Usage: babeltrace2 [GEN OPTS] query [OPTS] TYPE.PLUGIN.CLS OBJECT\n");
	fprintf(fp, "\n");
	fprintf(fp, "Options:\n");
	fprintf(fp, "\n");
	fprintf(fp, "  -p, --params=PARAMS  Set the query parameters to PARAMS (see the expected\n");
	fprintf(fp, "                       format of PARAMS below)\n");
	fprintf(fp, "  -h, --help           Show this help and quit\n");
	fprintf(fp, "\n\n");
	print_expected_params_format(fp);
}

static
const struct argpar_opt_descr query_options[] = {
	/* id, short_name, long_name, with_arg */
	{ OPT_HELP, 'h', "help", false },
	{ OPT_PARAMS, 'p', "params", true },
	ARGPAR_OPT_DESCR_SENTINEL
};

/*
 * Creates a Babeltrace config object from the arguments of a query
 * command.
 */
static
enum bt_config_cli_args_status bt_config_query_from_args(int argc,
		const char *argv[], struct bt_config **cfg_out,
		const bt_value *plugin_paths, int default_log_level,
		unsigned int consumed_args)
{
	enum bt_config_cli_args_status status;
	struct bt_config *cfg = NULL;
	const char *component_class_spec = NULL;
	const char *query_object = NULL;
	GString *error_str = NULL;
	struct argpar_iter *argpar_iter = NULL;
	const struct argpar_item *argpar_item = NULL;

	bt_value *params = bt_value_map_create();
	if (!params) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	cfg = bt_config_query_create(plugin_paths);
	if (!cfg) {
		goto error;
	}

	error_str = g_string_new(NULL);
	if (!error_str) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	argpar_iter = argpar_iter_create(argc, argv, query_options);
	if (!argpar_iter) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	while (true) {
		enum parse_next_item_status parse_status =
			parse_next_item(argpar_iter, &argpar_item, argv, "query",
				consumed_args);

		if (parse_status == PARSE_NEXT_ITEM_STATUS_ERROR) {
			goto error;
		} else if (parse_status == PARSE_NEXT_ITEM_STATUS_END) {
			break;
		}

		if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) {
			const struct argpar_opt_descr *opt_descr =
				argpar_item_opt_descr(argpar_item);
			const char *arg = argpar_item_opt_arg(argpar_item);

			switch (opt_descr->id) {
			case OPT_HELP:
				print_query_usage(stdout);
				status = BT_CONFIG_CLI_ARGS_STATUS_INFO_ONLY;
				goto end;
			case OPT_PARAMS:
			{
				bt_value *parsed_params = bt_param_parse(arg, error_str);
				bt_value_map_extend_status extend_status;
				if (!parsed_params) {
					BT_CLI_LOGE_APPEND_CAUSE("Invalid format for --params option's argument:\n    %s",
						error_str->str);
					goto error;
				}

				extend_status = bt_value_map_extend(params, parsed_params);
				BT_VALUE_PUT_REF_AND_RESET(parsed_params);
				if (extend_status) {
					BT_CLI_LOGE_APPEND_CAUSE("Cannot extend current parameters with --params option's argument:\n    %s",
						arg);
					goto error;
				}
				break;
			}
			default:
				bt_common_abort();
			}
		} else {
			const char *arg = argpar_item_non_opt_arg(argpar_item);

			/*
			 * We need exactly two non-option arguments
			 * which are the mandatory component class
			 * specification and query object.
			 */
			if (!component_class_spec) {
				component_class_spec = arg;
			} else if (!query_object) {
				query_object = arg;
			} else {
				BT_CLI_LOGE_APPEND_CAUSE("Extraneous command-line argument specified to `query` command: `%s`.",
					arg);
				goto error;
			}
		}
	}

	if (!component_class_spec || !query_object) {
		print_query_usage(stdout);
		status = BT_CONFIG_CLI_ARGS_STATUS_INFO_ONLY;
		goto end;
	}

	cfg->cmd_data.query.cfg_component =
		bt_config_component_from_arg(component_class_spec,
			default_log_level);
	if (!cfg->cmd_data.query.cfg_component) {
		BT_CLI_LOGE_APPEND_CAUSE("Invalid format for component class specification:\n    %s",
			component_class_spec);
		goto error;
	}

	BT_ASSERT(params);
	BT_OBJECT_MOVE_REF(cfg->cmd_data.query.cfg_component->params, params);

	if (strlen(query_object) == 0) {
		BT_CLI_LOGE_APPEND_CAUSE("Invalid empty object.");
		goto error;
	}

	g_string_assign(cfg->cmd_data.query.object, query_object);
	BT_OBJECT_MOVE_REF(*cfg_out, cfg);
	status = BT_CONFIG_CLI_ARGS_STATUS_OK;
	goto end;

error:
	status = BT_CONFIG_CLI_ARGS_STATUS_ERROR;

end:
	argpar_iter_destroy(argpar_iter);
	argpar_item_destroy(argpar_item);

	if (error_str) {
		g_string_free(error_str, TRUE);
	}

	bt_value_put_ref(params);
	bt_object_put_ref(cfg);

	return status;
}

/*
 * Prints the list-plugins command usage.
 */
static
void print_list_plugins_usage(FILE *fp)
{
	fprintf(fp, "Usage: babeltrace2 [GENERAL OPTIONS] list-plugins [OPTIONS]\n");
	fprintf(fp, "\n");
	fprintf(fp, "Options:\n");
	fprintf(fp, "\n");
	fprintf(fp, "  -h, --help                        Show this help and quit\n");
	fprintf(fp, "\n");
	fprintf(fp, "See `babeltrace2 --help` for the list of general options.\n");
	fprintf(fp, "\n");
	fprintf(fp, "Use `babeltrace2 help` to get help for a specific plugin or component class.\n");
}

static
const struct argpar_opt_descr list_plugins_options[] = {
	/* id, short_name, long_name, with_arg */
	{ OPT_HELP, 'h', "help", false },
	ARGPAR_OPT_DESCR_SENTINEL
};

/*
 * Creates a Babeltrace config object from the arguments of a
 * list-plugins command.
 */
static
enum bt_config_cli_args_status bt_config_list_plugins_from_args(int argc,
		const char *argv[], struct bt_config **cfg_out,
		const bt_value *plugin_paths, unsigned int consumed_args)
{
	enum bt_config_cli_args_status status;
	struct bt_config *cfg = NULL;
	struct argpar_iter *argpar_iter = NULL;
	const struct argpar_item *argpar_item = NULL;

	cfg = bt_config_list_plugins_create(plugin_paths);
	if (!cfg) {
		goto error;
	}

	argpar_iter = argpar_iter_create(argc, argv, list_plugins_options);
	if (!argpar_iter) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	while (true) {
		enum parse_next_item_status parse_status =
			parse_next_item(argpar_iter, &argpar_item, argv, "list-plugins",
				consumed_args);

		if (parse_status == PARSE_NEXT_ITEM_STATUS_ERROR) {
			goto error;
		} else if (parse_status == PARSE_NEXT_ITEM_STATUS_END) {
			break;
		}

		if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) {
			const struct argpar_opt_descr *opt_descr =
				argpar_item_opt_descr(argpar_item);

			switch (opt_descr->id) {
			case OPT_HELP:
				print_list_plugins_usage(stdout);
				status = BT_CONFIG_CLI_ARGS_STATUS_INFO_ONLY;
				goto end;
			default:
				bt_common_abort();
			}
		} else {
			BT_CLI_LOGE_APPEND_CAUSE(
				"Extraneous command-line argument specified to `list-plugins` command: `%s`.",
				argpar_item_non_opt_arg(argpar_item));
			goto error;
		}
		}

	BT_OBJECT_MOVE_REF(*cfg_out, cfg);
	status = BT_CONFIG_CLI_ARGS_STATUS_OK;
	goto end;

error:
	status = BT_CONFIG_CLI_ARGS_STATUS_ERROR;

end:
	argpar_iter_destroy(argpar_iter);
	argpar_item_destroy(argpar_item);
	bt_object_put_ref(cfg);

	return status;
}

/*
 * Prints the run command usage.
 */
static
void print_run_usage(FILE *fp)
{
	fprintf(fp, "Usage: babeltrace2 [GENERAL OPTIONS] run [OPTIONS]\n");
	fprintf(fp, "\n");
	fprintf(fp, "Options:\n");
	fprintf(fp, "\n");
	fprintf(fp, "  -b, --base-params=PARAMS          Set PARAMS as the current base parameters\n");
	fprintf(fp, "                                    for all the following components until\n");
	fprintf(fp, "                                    --reset-base-params is encountered\n");
	fprintf(fp, "                                    (see the expected format of PARAMS below)\n");
	fprintf(fp, "  -c, --component=NAME:TYPE.PLUGIN.CLS\n");
	fprintf(fp, "                                    Instantiate the component class CLS of type\n");
	fprintf(fp, "                                    TYPE (`source`, `filter`, or `sink`) found\n");
	fprintf(fp, "                                    in the plugin PLUGIN, add it to the graph,\n");
	fprintf(fp, "                                    and name it NAME");
	fprintf(fp, "  -x, --connect=CONNECTION          Connect two created components (see the\n");
	fprintf(fp, "                                    expected format of CONNECTION below)\n");
	fprintf(fp, "  -l, --log-level=LVL               Set the log level of the current component to LVL\n");
	fprintf(fp, "                                    (`N`, `T`, `D`, `I`, `W`, `E`, or `F`)\n");
	fprintf(fp, "  -p, --params=PARAMS               Add initialization parameters PARAMS to the\n");
	fprintf(fp, "                                    current component (see the expected format\n");
	fprintf(fp, "                                    of PARAMS below)\n");
	fprintf(fp, "  -r, --reset-base-params           Reset the current base parameters to an\n");
	fprintf(fp, "                                    empty map\n");
	fprintf(fp, "      --retry-duration=DUR          When babeltrace2(1) needs to retry to run\n");
	fprintf(fp, "                                    the graph later, retry in DUR µs\n");
	fprintf(fp, "                                    (default: 100000)\n");
	fprintf(fp, "  -h, --help                        Show this help and quit\n");
	fprintf(fp, "\n");
	fprintf(fp, "See `babeltrace2 --help` for the list of general options.\n");
	fprintf(fp, "\n\n");
	fprintf(fp, "Expected format of CONNECTION\n");
	fprintf(fp, "-----------------------------\n");
	fprintf(fp, "\n");
	fprintf(fp, "    UPSTREAM[.UPSTREAM-PORT]:DOWNSTREAM[.DOWNSTREAM-PORT]\n");
	fprintf(fp, "\n");
	fprintf(fp, "UPSTREAM and DOWNSTREAM are names of the upstream and downstream\n");
	fprintf(fp, "components to connect together. You must escape the following characters\n\n");
	fprintf(fp, "with `\\`: `\\`, `.`, and `:`. You must set the name of the current\n");
	fprintf(fp, "component using the NAME prefix of the --component option.\n");
	fprintf(fp, "\n");
	fprintf(fp, "UPSTREAM-PORT and DOWNSTREAM-PORT are optional globbing patterns to\n");
	fprintf(fp, "identify the upstream and downstream ports to use for the connection.\n");
	fprintf(fp, "When the port is not specified, `*` is used.\n");
	fprintf(fp, "\n");
	fprintf(fp, "When a component named UPSTREAM has an available port which matches the\n");
	fprintf(fp, "UPSTREAM-PORT globbing pattern, it is connected to the first port which\n");
	fprintf(fp, "matches the DOWNSTREAM-PORT globbing pattern of the component named\n");
	fprintf(fp, "DOWNSTREAM.\n");
	fprintf(fp, "\n");
	fprintf(fp, "The only special character in UPSTREAM-PORT and DOWNSTREAM-PORT is `*`\n");
	fprintf(fp, "which matches anything. You must escape the following characters\n");
	fprintf(fp, "with `\\`: `\\`, `*`, `?`, `[`, `.`, and `:`.\n");
	fprintf(fp, "\n");
	fprintf(fp, "You can connect a source component to a filter or sink component. You\n");
	fprintf(fp, "can connect a filter component to a sink component.\n");
	fprintf(fp, "\n");
	fprintf(fp, "Examples:\n");
	fprintf(fp, "\n");
	fprintf(fp, "    my-src:my-sink\n");
	fprintf(fp, "    ctf-fs.*stream*:utils-muxer:*\n");
	fprintf(fp, "\n");
	fprintf(fp, "IMPORTANT: Make sure to single-quote the whole argument when you run\n");
	fprintf(fp, "babeltrace2 from a shell.\n");
	fprintf(fp, "\n\n");
	print_expected_params_format(fp);
}

/*
 * Creates a Babeltrace config object from the arguments of a run
 * command.
 */
static
enum bt_config_cli_args_status bt_config_run_from_args(int argc, const char *argv[],
		struct bt_config **cfg_out, const bt_value *plugin_paths,
		int default_log_level, unsigned int consumed_args)
{
	enum bt_config_cli_args_status status;
	struct bt_config_component *cur_cfg_comp = NULL;
	bt_value *cur_base_params = NULL;
	int ret = 0;
	struct bt_config *cfg = NULL;
	bt_value *instance_names = NULL;
	bt_value *connection_args = NULL;
	char error_buf[256] = { 0 };
	long retry_duration = -1;
	bt_value_map_extend_status extend_status;
	GString *error_str = NULL;
	struct argpar_iter *argpar_iter = NULL;
	const struct argpar_item *argpar_item = NULL;

	static const struct argpar_opt_descr run_options[] = {
		{ OPT_BASE_PARAMS, 'b', "base-params", true },
		{ OPT_COMPONENT, 'c', "component", true },
		{ OPT_CONNECT, 'x', "connect", true },
		{ OPT_HELP, 'h', "help", false },
		{ OPT_LOG_LEVEL, 'l', "log-level", true },
		{ OPT_PARAMS, 'p', "params", true },
		{ OPT_RESET_BASE_PARAMS, 'r', "reset-base-params", false },
		{ OPT_RETRY_DURATION, '\0', "retry-duration", true },
		ARGPAR_OPT_DESCR_SENTINEL
	};

	error_str = g_string_new(NULL);
	if (!error_str) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	if (argc < 1) {
		print_run_usage(stdout);
		status = BT_CONFIG_CLI_ARGS_STATUS_INFO_ONLY;
		goto end;
	}

	cfg = bt_config_run_create(plugin_paths);
	if (!cfg) {
		goto error;
	}

	cfg->cmd_data.run.retry_duration_us = 100000;
	cur_base_params = bt_value_map_create();
	if (!cur_base_params) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	instance_names = bt_value_map_create();
	if (!instance_names) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	connection_args = bt_value_array_create();
	if (!connection_args) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	argpar_iter = argpar_iter_create(argc, argv, run_options);
	if (!argpar_iter) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	while (true) {
		enum parse_next_item_status parse_status;
		const struct argpar_opt_descr *opt_descr;
		const char *arg;

		parse_status = parse_next_item(argpar_iter, &argpar_item, argv, "run",
			consumed_args);
		if (parse_status == PARSE_NEXT_ITEM_STATUS_ERROR) {
			goto error;
		} else if (parse_status == PARSE_NEXT_ITEM_STATUS_END) {
			break;
		}

		/* This command does not accept non-option arguments.*/
		if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_NON_OPT) {
			BT_CLI_LOGE_APPEND_CAUSE("Unexpected argument: `%s`",
				argpar_item_non_opt_arg(argpar_item));
			goto error;
		}

		opt_descr = argpar_item_opt_descr(argpar_item);
		arg = argpar_item_opt_arg(argpar_item);

		switch (opt_descr->id) {
		case OPT_HELP:
			print_run_usage(stdout);
			status = BT_CONFIG_CLI_ARGS_STATUS_INFO_ONLY;
			goto end;
		case OPT_COMPONENT:
		{
			enum bt_config_component_dest dest;

			BT_OBJECT_PUT_REF_AND_RESET(cur_cfg_comp);
			cur_cfg_comp = bt_config_component_from_arg(arg,
				default_log_level);
			if (!cur_cfg_comp) {
				BT_CLI_LOGE_APPEND_CAUSE("Invalid format for --component option's argument:\n    %s",
					arg);
				goto error;
			}

			switch (cur_cfg_comp->type) {
			case BT_COMPONENT_CLASS_TYPE_SOURCE:
				dest = BT_CONFIG_COMPONENT_DEST_SOURCE;
				break;
			case BT_COMPONENT_CLASS_TYPE_FILTER:
				dest = BT_CONFIG_COMPONENT_DEST_FILTER;
				break;
			case BT_COMPONENT_CLASS_TYPE_SINK:
				dest = BT_CONFIG_COMPONENT_DEST_SINK;
				break;
			default:
				bt_common_abort();
			}

			BT_ASSERT(cur_base_params);
			bt_value_put_ref(cur_cfg_comp->params);
			if (bt_value_copy(cur_base_params,
					&cur_cfg_comp->params) < 0) {
				BT_CLI_LOGE_APPEND_CAUSE_OOM();
				goto error;
			}

			ret = add_run_cfg_comp_check_name(cfg,
				cur_cfg_comp, dest,
				instance_names);
			if (ret) {
				goto error;
			}

			break;
		}
		case OPT_PARAMS:
		{
			bt_value *params;

			if (!cur_cfg_comp) {
				BT_CLI_LOGE_APPEND_CAUSE("Cannot add parameters to unavailable component:\n    %s",
					arg);
				goto error;
			}

			params = bt_param_parse(arg, error_str);
			if (!params) {
				BT_CLI_LOGE_APPEND_CAUSE("Invalid format for --params option's argument:\n    %s",
					error_str->str);
				goto error;
			}

			extend_status = bt_value_map_extend(cur_cfg_comp->params,
				params);
			BT_VALUE_PUT_REF_AND_RESET(params);
			if (extend_status != BT_VALUE_MAP_EXTEND_STATUS_OK) {
				BT_CLI_LOGE_APPEND_CAUSE("Cannot extend current component parameters with --params option's argument:\n    %s",
					arg);
				goto error;
			}

			break;
		}
		case OPT_LOG_LEVEL:
			if (!cur_cfg_comp) {
				BT_CLI_LOGE_APPEND_CAUSE("Cannot set the log level of unavailable component:\n    %s",
					arg);
				goto error;
			}

			cur_cfg_comp->log_level =
				bt_log_get_level_from_string(arg);
			if (cur_cfg_comp->log_level < 0) {
				BT_CLI_LOGE_APPEND_CAUSE("Invalid argument for --log-level option:\n    %s",
					arg);
				goto error;
			}
			break;
		case OPT_BASE_PARAMS:
		{
			bt_value *params = bt_param_parse(arg, error_str);

			if (!params) {
				BT_CLI_LOGE_APPEND_CAUSE("Invalid format for --base-params option's argument:\n    %s",
					error_str->str);
				goto error;
			}

			BT_OBJECT_MOVE_REF(cur_base_params, params);
			break;
		}
		case OPT_RESET_BASE_PARAMS:
			BT_VALUE_PUT_REF_AND_RESET(cur_base_params);
			cur_base_params = bt_value_map_create();
			if (!cur_base_params) {
				BT_CLI_LOGE_APPEND_CAUSE_OOM();
				goto error;
			}
			break;
		case OPT_CONNECT:
			if (bt_value_array_append_string_element(
					connection_args, arg)) {
				BT_CLI_LOGE_APPEND_CAUSE_OOM();
				goto error;
			}
			break;
		case OPT_RETRY_DURATION: {
			gchar *end;
			size_t arg_len = strlen(arg);

			retry_duration = g_ascii_strtoll(arg, &end, 10);

			if (arg_len == 0 || end != (arg + arg_len)) {
				BT_CLI_LOGE_APPEND_CAUSE(
					"Could not parse --retry-duration option's argument as an unsigned integer: `%s`",
					arg);
				goto error;
			}

			if (retry_duration < 0) {
				BT_CLI_LOGE_APPEND_CAUSE("--retry-duration option's argument must be positive or 0: %ld",
					retry_duration);
				goto error;
			}

			cfg->cmd_data.run.retry_duration_us =
				(uint64_t) retry_duration;
			break;
		}
		default:
			bt_common_abort();
		}
	}

	BT_OBJECT_PUT_REF_AND_RESET(cur_cfg_comp);

	if (cfg->cmd_data.run.sources->len == 0) {
		BT_CLI_LOGE_APPEND_CAUSE("Incomplete graph: no source component.");
		goto error;
	}

	if (cfg->cmd_data.run.sinks->len == 0) {
		BT_CLI_LOGE_APPEND_CAUSE("Incomplete graph: no sink component.");
		goto error;
	}

	ret = bt_config_cli_args_create_connections(cfg,
		connection_args,
		error_buf, 256);
	if (ret) {
		BT_CLI_LOGE_APPEND_CAUSE("Cannot creation connections:\n%s", error_buf);
		goto error;
	}

	BT_OBJECT_MOVE_REF(*cfg_out, cfg);
	status = BT_CONFIG_CLI_ARGS_STATUS_OK;
	goto end;

error:
	status = BT_CONFIG_CLI_ARGS_STATUS_ERROR;

end:
	if (error_str) {
		g_string_free(error_str, TRUE);
	}

	argpar_iter_destroy(argpar_iter);
	argpar_item_destroy(argpar_item);

	BT_OBJECT_PUT_REF_AND_RESET(cur_cfg_comp);
	BT_VALUE_PUT_REF_AND_RESET(cur_base_params);
	BT_VALUE_PUT_REF_AND_RESET(instance_names);
	BT_VALUE_PUT_REF_AND_RESET(connection_args);
	bt_object_put_ref(cfg);

	return status;
}

static
enum bt_config_cli_args_status bt_config_run_from_args_array(
		const bt_value *run_args, struct bt_config **cfg,
		const bt_value *plugin_paths,
		int default_log_level)
{
	enum bt_config_cli_args_status status;
	const char **argv;
	uint64_t i, len = bt_value_array_get_length(run_args);

	BT_ASSERT(len <= SIZE_MAX);
	argv = calloc((size_t) len, sizeof(*argv));
	if (!argv) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	for (i = 0; i < len; i++) {
		const bt_value *arg_value =
			bt_value_array_borrow_element_by_index_const(run_args,
				i);
		const char *arg;

		arg = bt_value_string_get(arg_value);
		BT_ASSERT(arg);
		argv[i] = arg;
	}

	status = bt_config_run_from_args((int) len, argv, cfg,
		plugin_paths, default_log_level, 0);

	goto end;

error:
	status = BT_CONFIG_CLI_ARGS_STATUS_ERROR;

end:
	free(argv);
	return status;
}

/*
 * Prints the convert command usage.
 */
static
void print_convert_usage(FILE *fp)
{
	fprintf(fp, "Usage: babeltrace2 [GENERAL OPTIONS] [convert] [OPTIONS] [PATH/URL]\n");
	fprintf(fp, "\n");
	fprintf(fp, "Options:\n");
	fprintf(fp, "\n");
	fprintf(fp, "  -c, --component=[NAME:]TYPE.PLUGIN.CLS\n");
	fprintf(fp, "                                    Instantiate the component class CLS of type\n");
	fprintf(fp, "                                    TYPE (`source`, `filter`, or `sink`) found\n");
	fprintf(fp, "                                    in the plugin PLUGIN, add it to the\n");
	fprintf(fp, "                                    conversion graph, and optionally name it\n");
	fprintf(fp, "                                    NAME\n");
	fprintf(fp, "  -l, --log-level=LVL               Set the log level of the current component to LVL\n");
	fprintf(fp, "                                    (`N`, `T`, `D`, `I`, `W`, `E`, or `F`)\n");
	fprintf(fp, "  -p, --params=PARAMS               Add initialization parameters PARAMS to the\n");
	fprintf(fp, "                                    current component (see the expected format\n");
	fprintf(fp, "                                    of PARAMS below)\n");
	fprintf(fp, "      --retry-duration=DUR          When babeltrace2(1) needs to retry to run\n");
	fprintf(fp, "                                    the graph later, retry in DUR µs\n");
	fprintf(fp, "                                    (default: 100000)\n");
	fprintf(fp, "                                    dynamic plugins can be loaded\n");
	fprintf(fp, "      --run-args                    Print the equivalent arguments for the\n");
	fprintf(fp, "                                    `run` command to the standard output,\n");
	fprintf(fp, "                                    formatted for a shell, and quit\n");
	fprintf(fp, "      --run-args-0                  Print the equivalent arguments for the\n");
	fprintf(fp, "                                    `run` command to the standard output,\n");
	fprintf(fp, "                                    formatted for `xargs -0`, and quit\n");
	fprintf(fp, "      --stream-intersection         Only process events when all streams\n");
	fprintf(fp, "                                    are active\n");
	fprintf(fp, "  -h, --help                        Show this help and quit\n");
	fprintf(fp, "\n");
	fprintf(fp, "Implicit `source.ctf.fs` component options:\n");
	fprintf(fp, "\n");
	fprintf(fp, "      --clock-force-correlate       Force the origin of all clocks\n");
	fprintf(fp, "                                    to the Unix epoch\n");
	fprintf(fp, "      --clock-offset=SEC            Set clock offset to SEC seconds\n");
	fprintf(fp, "      --clock-offset-ns=NS          Set clock offset to NS ns\n");
	fprintf(fp, "\n");
	fprintf(fp, "Implicit `sink.text.pretty` component options:\n");
	fprintf(fp, "\n");
	fprintf(fp, "      --clock-cycles                Print timestamps in clock cycles\n");
	fprintf(fp, "      --clock-date                  Print timestamp dates\n");
	fprintf(fp, "      --clock-gmt                   Print and parse timestamps in the GMT\n");
	fprintf(fp, "                                    time zone instead of the local time zone\n");
	fprintf(fp, "      --clock-seconds               Print the timestamps as `SEC.NS` instead\n");
	fprintf(fp, "                                    of `hh:mm:ss.nnnnnnnnn`\n");
	fprintf(fp, "      --color=(never | auto | always)\n");
	fprintf(fp, "                                    Never, automatically, or always emit\n");
	fprintf(fp, "                                    console color codes\n");
	fprintf(fp, "  -f, --fields=FIELD[,FIELD]...     Print additional fields; FIELD can be:\n");
	fprintf(fp, "                                      `all`, `trace`, `trace:hostname`,\n");
	fprintf(fp, "                                      `trace:domain`, `trace:procname`,\n");
	fprintf(fp, "                                      `trace:vpid`, `loglevel`, `emf`\n");
	fprintf(fp, "  -n, --names=NAME[,NAME]...        Print field names; NAME can be:\n");
	fprintf(fp, "                                      `payload` (or `arg` or `args`), `none`,\n");
	fprintf(fp, "                                      `all`, `scope`, `header`, `context`\n");
	fprintf(fp, "                                      (or `ctx`)\n");
	fprintf(fp, "      --no-delta                    Do not print time delta between\n");
	fprintf(fp, "                                    consecutive events\n");
	fprintf(fp, "  -w, --output=PATH                 Write output text to PATH instead of\n");
	fprintf(fp, "                                    the standard output\n");
	fprintf(fp, "\n");
	fprintf(fp, "Implicit `filter.utils.trimmer` component options:\n");
	fprintf(fp, "\n");
	fprintf(fp, "  -b, --begin=BEGIN                 Set the beginning time of the conversion\n");
	fprintf(fp, "                                    time range to BEGIN (see the format of\n");
	fprintf(fp, "                                    BEGIN below)\n");
	fprintf(fp, "  -e, --end=END                     Set the end time of the conversion time\n");
	fprintf(fp, "                                    range to END (see the format of END below)\n");
	fprintf(fp, "  -t, --timerange=TIMERANGE         Set conversion time range to TIMERANGE:\n");
	fprintf(fp, "                                    BEGIN,END or [BEGIN,END] (literally `[` and\n");
	fprintf(fp, "                                    `]`) (see the format of BEGIN/END below)\n");
	fprintf(fp, "\n");
	fprintf(fp, "Implicit `filter.lttng-utils.debug-info` component options:\n");
	fprintf(fp, "\n");
	fprintf(fp, "      --debug-info                  Create an implicit\n");
	fprintf(fp, "                                    `filter.lttng-utils.debug-info` component\n");
	fprintf(fp, "      --debug-info-dir=DIR          Search for debug info in directory DIR\n");
	fprintf(fp, "                                    instead of `/usr/lib/debug`\n");
	fprintf(fp, "      --debug-info-full-path        Show full debug info source and\n");
	fprintf(fp, "                                    binary paths instead of just names\n");
	fprintf(fp, "      --debug-info-target-prefix=DIR\n");
	fprintf(fp, "                                    Use directory DIR as a prefix when\n");
	fprintf(fp, "                                    looking up executables during debug\n");
	fprintf(fp, "                                    info analysis\n");
	fprintf(fp, "\n");
	fprintf(fp, "Legacy options that still work:\n");
	fprintf(fp, "\n");
	fprintf(fp, "  -i, --input-format=(ctf | lttng-live)\n");
	fprintf(fp, "                                    `ctf`:\n");
	fprintf(fp, "                                      Create an implicit `source.ctf.fs`\n");
	fprintf(fp, "                                      component\n");
	fprintf(fp, "                                    `lttng-live`:\n");
	fprintf(fp, "                                      Create an implicit `source.ctf.lttng-live`\n");
	fprintf(fp, "                                      component\n");
	fprintf(fp, "  -o, --output-format=(text | ctf | dummy | ctf-metadata)\n");
	fprintf(fp, "                                    `text`:\n");
	fprintf(fp, "                                      Create an implicit `sink.text.pretty`\n");
	fprintf(fp, "                                      component\n");
	fprintf(fp, "                                    `ctf`:\n");
	fprintf(fp, "                                      Create an implicit `sink.ctf.fs`\n");
	fprintf(fp, "                                      component\n");
	fprintf(fp, "                                    `dummy`:\n");
	fprintf(fp, "                                      Create an implicit `sink.utils.dummy`\n");
	fprintf(fp, "                                      component\n");
	fprintf(fp, "                                    `ctf-metadata`:\n");
	fprintf(fp, "                                      Query the `source.ctf.fs` component class\n");
	fprintf(fp, "                                      for metadata text and quit\n");
	fprintf(fp, "\n");
	fprintf(fp, "See `babeltrace2 --help` for the list of general options.\n");
	fprintf(fp, "\n\n");
	fprintf(fp, "Format of BEGIN and END\n");
	fprintf(fp, "-----------------------\n");
	fprintf(fp, "\n");
	fprintf(fp, "    [YYYY-MM-DD [hh:mm:]]ss[.nnnnnnnnn]\n");
	fprintf(fp, "\n\n");
	print_expected_params_format(fp);
}

static
const struct argpar_opt_descr convert_options[] = {
	/* id, short_name, long_name, with_arg */
	{ OPT_BEGIN, 'b', "begin", true },
	{ OPT_CLOCK_CYCLES, '\0', "clock-cycles", false },
	{ OPT_CLOCK_DATE, '\0', "clock-date", false },
	{ OPT_CLOCK_FORCE_CORRELATE, '\0', "clock-force-correlate", false },
	{ OPT_CLOCK_GMT, '\0', "clock-gmt", false },
	{ OPT_CLOCK_OFFSET, '\0', "clock-offset", true },
	{ OPT_CLOCK_OFFSET_NS, '\0', "clock-offset-ns", true },
	{ OPT_CLOCK_SECONDS, '\0', "clock-seconds", false },
	{ OPT_COLOR, '\0', "color", true },
	{ OPT_COMPONENT, 'c', "component", true },
	{ OPT_DEBUG, 'd', "debug", false },
	{ OPT_DEBUG_INFO_DIR, '\0', "debug-info-dir", true },
	{ OPT_DEBUG_INFO_FULL_PATH, '\0', "debug-info-full-path", false },
	{ OPT_DEBUG_INFO_TARGET_PREFIX, '\0', "debug-info-target-prefix", true },
	{ OPT_END, 'e', "end", true },
	{ OPT_FIELDS, 'f', "fields", true },
	{ OPT_HELP, 'h', "help", false },
	{ OPT_INPUT_FORMAT, 'i', "input-format", true },
	{ OPT_LOG_LEVEL, 'l', "log-level", true },
	{ OPT_NAMES, 'n', "names", true },
	{ OPT_DEBUG_INFO, '\0', "debug-info", false },
	{ OPT_NO_DELTA, '\0', "no-delta", false },
	{ OPT_OMIT_HOME_PLUGIN_PATH, '\0', "omit-home-plugin-path", false },
	{ OPT_OMIT_SYSTEM_PLUGIN_PATH, '\0', "omit-system-plugin-path", false },
	{ OPT_OUTPUT, 'w', "output", true },
	{ OPT_OUTPUT_FORMAT, 'o', "output-format", true },
	{ OPT_PARAMS, 'p', "params", true },
	{ OPT_PLUGIN_PATH, '\0', "plugin-path", true },
	{ OPT_RETRY_DURATION, '\0', "retry-duration", true },
	{ OPT_RUN_ARGS, '\0', "run-args", false },
	{ OPT_RUN_ARGS_0, '\0', "run-args-0", false },
	{ OPT_STREAM_INTERSECTION, '\0', "stream-intersection", false },
	{ OPT_TIMERANGE, '\0', "timerange", true },
	{ OPT_VERBOSE, 'v', "verbose", false },
	ARGPAR_OPT_DESCR_SENTINEL
};

static
GString *get_component_auto_name(const char *prefix,
		const bt_value *existing_names)
{
	unsigned int i = 0;
	GString *auto_name = g_string_new(NULL);

	if (!auto_name) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto end;
	}

	if (!bt_value_map_has_entry(existing_names, prefix)) {
		g_string_assign(auto_name, prefix);
		goto end;
	}

	do {
		g_string_printf(auto_name, "%s-%d", prefix, i);
		i++;
	} while (bt_value_map_has_entry(existing_names, auto_name->str));

end:
	return auto_name;
}

struct implicit_component_args {
	bool exists;

	/* The component class name (e.g. src.ctf.fs). */
	GString *comp_arg;

	/* The component instance name. */
	GString *name_arg;

	GString *params_arg;
	bt_value *extra_params;
};

static
int assign_name_to_implicit_component(struct implicit_component_args *args,
		const char *prefix, bt_value *existing_names,
		GList **comp_names, bool append_to_comp_names)
{
	int ret = 0;
	GString *name = NULL;

	if (!args->exists) {
		goto end;
	}

	name = get_component_auto_name(prefix,
		existing_names);

	if (!name) {
		ret = -1;
		goto end;
	}

	g_string_assign(args->name_arg, name->str);

	if (bt_value_map_insert_entry(existing_names, name->str,
			bt_value_null)) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		ret = -1;
		goto end;
	}

	if (append_to_comp_names) {
		*comp_names = g_list_append(*comp_names, name);
		name = NULL;
	}

end:
	if (name) {
		g_string_free(name, TRUE);
	}

	return ret;
}

static
int append_run_args_for_implicit_component(
		struct implicit_component_args *impl_args,
		bt_value *run_args)
{
	int ret = 0;
	uint64_t i;
	GString *component_arg_for_run = NULL;

	if (!impl_args->exists) {
		goto end;
	}

	component_arg_for_run = g_string_new(NULL);
	if (!component_arg_for_run) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	/* Build the full `name:type.plugin.cls`. */
	BT_ASSERT(!strchr(impl_args->name_arg->str, '\\'));
	BT_ASSERT(!strchr(impl_args->name_arg->str, ':'));
	g_string_printf(component_arg_for_run, "%s:%s",
		impl_args->name_arg->str, impl_args->comp_arg->str);

	if (bt_value_array_append_string_element(run_args, "--component")) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	if (bt_value_array_append_string_element(run_args,
			component_arg_for_run->str)) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	if (impl_args->params_arg->len > 0) {
		if (bt_value_array_append_string_element(run_args, "--params")) {
			BT_CLI_LOGE_APPEND_CAUSE_OOM();
			goto error;
		}

		if (bt_value_array_append_string_element(run_args,
				impl_args->params_arg->str)) {
			BT_CLI_LOGE_APPEND_CAUSE_OOM();
			goto error;
		}
	}

	for (i = 0; i < bt_value_array_get_length(impl_args->extra_params); i++) {
		const bt_value *elem;
		const char *arg;

		elem = bt_value_array_borrow_element_by_index(
			impl_args->extra_params, i);

		BT_ASSERT(bt_value_is_string(elem));
		arg = bt_value_string_get(elem);
		ret = bt_value_array_append_string_element(run_args, arg);
		if (ret) {
			BT_CLI_LOGE_APPEND_CAUSE_OOM();
			goto error;
		}
	}

	goto end;

error:
	ret = -1;

end:
	if (component_arg_for_run) {
		g_string_free(component_arg_for_run, TRUE);
	}

	return ret;
}

/* Free the fields of a `struct implicit_component_args`. */

static
void finalize_implicit_component_args(struct implicit_component_args *args)
{
	BT_ASSERT(args);

	if (args->comp_arg) {
		g_string_free(args->comp_arg, TRUE);
	}

	if (args->name_arg) {
		g_string_free(args->name_arg, TRUE);
	}

	if (args->params_arg) {
		g_string_free(args->params_arg, TRUE);
	}

	bt_value_put_ref(args->extra_params);
}

/* Destroy a dynamically-allocated `struct implicit_component_args`. */

static
void destroy_implicit_component_args(struct implicit_component_args *args)
{
	finalize_implicit_component_args(args);
	g_free(args);
}

/* Initialize the fields of an already allocated `struct implicit_component_args`. */

static
int init_implicit_component_args(struct implicit_component_args *args,
		const char *comp_arg, bool exists)
{
	int ret = 0;

	args->exists = exists;
	args->comp_arg = g_string_new(comp_arg);
	args->name_arg = g_string_new(NULL);
	args->params_arg = g_string_new(NULL);
	args->extra_params = bt_value_array_create();

	if (!args->comp_arg || !args->name_arg ||
			!args->params_arg || !args->extra_params) {
		ret = -1;
		finalize_implicit_component_args(args);
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto end;
	}

end:
	return ret;
}

/* Dynamically allocate and initialize a `struct implicit_component_args`. */

static
struct implicit_component_args *create_implicit_component_args(
		const char *comp_arg)
{
	struct implicit_component_args *args;
	int status;

	args = g_new(struct implicit_component_args, 1);
	if (!args) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto end;
	}

	status = init_implicit_component_args(args, comp_arg, true);
	if (status != 0) {
		g_free(args);
		args = NULL;
	}

end:
	return args;
}

static
void append_implicit_component_param(struct implicit_component_args *args,
	const char *key, const char *value)
{
	BT_ASSERT(args);
	BT_ASSERT(key);
	BT_ASSERT(value);
	append_param_arg(args->params_arg, key, value);
}

/*
 * Append the given parameter (`key=value`) to all component specifications
 * in `implicit_comp_args` (an array of `struct implicit_component_args *`)
 * which match `comp_arg`.
 *
 * Return the number of matching components.
 */

static
int append_multiple_implicit_components_param(GPtrArray *implicit_comp_args,
		const char *comp_arg, const char *key, const char *value)
{
	int i;
	int n = 0;

	for (i = 0; i < implicit_comp_args->len; i++) {
		struct implicit_component_args *args = implicit_comp_args->pdata[i];

		if (strcmp(args->comp_arg->str, comp_arg) == 0) {
			append_implicit_component_param(args, key, value);
			n++;
		}
	}

	return n;
}

/* Escape value to make it suitable to use as a string parameter value. */
static
gchar *escape_string_value(const char *value)
{
	GString *ret;
	const char *in;

	ret = g_string_new(NULL);
	if (!ret) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto end;
	}

	in = value;
	while (*in) {
		switch (*in) {
		case '"':
		case '\\':
			g_string_append_c(ret, '\\');
			break;
		}

		g_string_append_c(ret, *in);

		in++;
	}

end:
	return g_string_free(ret, FALSE);
}

static
int bt_value_to_cli_param_value_append(const bt_value *value, GString *buf)
{
	BT_ASSERT(buf);

	int ret = -1;

	switch (bt_value_get_type(value)) {
	case BT_VALUE_TYPE_STRING:
	{
		const char *str_value = bt_value_string_get(value);
		gchar *escaped_str_value;

		escaped_str_value = escape_string_value(str_value);
		if (!escaped_str_value) {
			goto end;
		}

		g_string_append_printf(buf, "\"%s\"", escaped_str_value);

		g_free(escaped_str_value);
		break;
	}
	case BT_VALUE_TYPE_ARRAY: {
		g_string_append_c(buf, '[');
		uint64_t sz = bt_value_array_get_length(value);
		for (uint64_t i = 0; i < sz; i++) {
			const bt_value *item;

			if (i > 0) {
				g_string_append(buf, ", ");
			}

			item = bt_value_array_borrow_element_by_index_const(
				value, i);
			ret = bt_value_to_cli_param_value_append(item, buf);

			if (ret) {
				goto end;
			}
		}
		g_string_append_c(buf, ']');
		break;
	}
	default:
		bt_common_abort();
	}

	ret = 0;

end:
	return ret;
}

/*
 * Convert `value` to its equivalent representation as a command line parameter
 * value.
 */

static
gchar *bt_value_to_cli_param_value(bt_value *value)
{
	GString *buf;
	gchar *result = NULL;

	buf = g_string_new(NULL);
	if (!buf) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	if (bt_value_to_cli_param_value_append(value, buf)) {
		goto error;
	}

	result = g_string_free(buf, FALSE);
	buf = NULL;

	goto end;

error:
	if (buf) {
		g_string_free(buf, TRUE);
	}

end:
	return result;
}

static
int append_parameter_to_args(bt_value *args, const char *key, bt_value *value)
{
	BT_ASSERT(args);
	BT_ASSERT(bt_value_get_type(args) == BT_VALUE_TYPE_ARRAY);
	BT_ASSERT(key);
	BT_ASSERT(value);

	int ret = 0;
	gchar *str_value = NULL;
	GString *parameter = NULL;

	if (bt_value_array_append_string_element(args, "--params")) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		ret = -1;
		goto end;
	}

	str_value = bt_value_to_cli_param_value(value);
	if (!str_value) {
		ret = -1;
		goto end;
	}

	parameter = g_string_new(NULL);
	if (!parameter) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		ret = -1;
		goto end;
	}

	g_string_printf(parameter, "%s=%s", key, str_value);

	if (bt_value_array_append_string_element(args, parameter->str)) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		ret = -1;
		goto end;
	}

end:
	if (parameter) {
		g_string_free(parameter, TRUE);
		parameter = NULL;
	}

	if (str_value) {
		g_free(str_value);
		str_value = NULL;
	}

	return ret;
}

static
int append_string_parameter_to_args(bt_value *args, const char *key, const char *value)
{
	bt_value *str_value;
	int ret;

	str_value = bt_value_string_create_init(value);

	if (!str_value) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		ret = -1;
		goto end;
	}

	ret = append_parameter_to_args(args, key, str_value);

end:
	BT_VALUE_PUT_REF_AND_RESET(str_value);
	return ret;
}

static
int append_implicit_component_extra_param(struct implicit_component_args *args,
		const char *key, const char *value)
{
	return append_string_parameter_to_args(args->extra_params, key, value);
}

/*
 * Escapes `.`, `:`, and `\` of `input` with `\`.
 */
static
GString *escape_dot_colon(const char *input)
{
	GString *output = g_string_new(NULL);
	const char *ch;

	if (!output) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto end;
	}

	for (ch = input; *ch != '\0'; ch++) {
		if (*ch == '\\' || *ch == '.' || *ch == ':') {
			g_string_append_c(output, '\\');
		}

		g_string_append_c(output, *ch);
	}

end:
	return output;
}

/*
 * Appends a --connect option to a list of arguments. `upstream_name`
 * and `downstream_name` are escaped with escape_dot_colon() in this
 * function.
 */
static
int append_connect_arg(bt_value *run_args,
		const char *upstream_name, const char *downstream_name)
{
	int ret = 0;
	GString *e_upstream_name = escape_dot_colon(upstream_name);
	GString *e_downstream_name = escape_dot_colon(downstream_name);
	GString *arg = g_string_new(NULL);

	if (!e_upstream_name || !e_downstream_name || !arg) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		ret = -1;
		goto end;
	}

	ret = bt_value_array_append_string_element(run_args, "--connect");
	if (ret) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		ret = -1;
		goto end;
	}

	g_string_append(arg, e_upstream_name->str);
	g_string_append_c(arg, ':');
	g_string_append(arg, e_downstream_name->str);
	ret = bt_value_array_append_string_element(run_args, arg->str);
	if (ret) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		ret = -1;
		goto end;
	}

end:
	if (arg) {
		g_string_free(arg, TRUE);
	}

	if (e_upstream_name) {
		g_string_free(e_upstream_name, TRUE);
	}

	if (e_downstream_name) {
		g_string_free(e_downstream_name, TRUE);
	}

	return ret;
}

/*
 * Appends the run command's --connect options for the convert command.
 */
static
int convert_auto_connect(bt_value *run_args,
		GList *source_names, GList *filter_names,
		GList *sink_names)
{
	int ret = 0;
	GList *source_at = source_names;
	GList *filter_at = filter_names;
	GList *filter_prev;
	GList *sink_at = sink_names;

	BT_ASSERT(source_names);
	BT_ASSERT(filter_names);
	BT_ASSERT(sink_names);

	/* Connect all sources to the first filter */
	for (source_at = source_names; source_at; source_at = g_list_next(source_at)) {
		GString *source_name = source_at->data;
		GString *filter_name = filter_at->data;

		ret = append_connect_arg(run_args, source_name->str,
			filter_name->str);
		if (ret) {
			goto error;
		}
	}

	filter_prev = filter_at;
	filter_at = g_list_next(filter_at);

	/* Connect remaining filters */
	for (; filter_at; filter_prev = filter_at, filter_at = g_list_next(filter_at)) {
		GString *filter_name = filter_at->data;
		GString *filter_prev_name = filter_prev->data;

		ret = append_connect_arg(run_args, filter_prev_name->str,
			filter_name->str);
		if (ret) {
			goto error;
		}
	}

	/* Connect last filter to all sinks */
	for (sink_at = sink_names; sink_at; sink_at = g_list_next(sink_at)) {
		GString *filter_name = filter_prev->data;
		GString *sink_name = sink_at->data;

		ret = append_connect_arg(run_args, filter_name->str,
			sink_name->str);
		if (ret) {
			goto error;
		}
	}

	goto end;

error:
	ret = -1;

end:
	return ret;
}

static
int split_timerange(const char *arg, char **begin, char **end)
{
	int ret = 0;
	const char *ch = arg;
	size_t end_pos;
	GString *g_begin = NULL;
	GString *g_end = NULL;

	BT_ASSERT(arg);

	if (*ch == '[') {
		ch++;
	}

	g_begin = bt_common_string_until(ch, "", ",", &end_pos);
	if (!g_begin || ch[end_pos] != ',' || g_begin->len == 0) {
		goto error;
	}

	ch += end_pos + 1;

	g_end = bt_common_string_until(ch, "", "]", &end_pos);
	if (!g_end || g_end->len == 0) {
		goto error;
	}

	BT_ASSERT(begin);
	BT_ASSERT(end);
	*begin = g_begin->str;
	*end = g_end->str;
	g_string_free(g_begin, FALSE);
	g_string_free(g_end, FALSE);
	g_begin = NULL;
	g_end = NULL;
	goto end;

error:
	ret = -1;

end:
	if (g_begin) {
		g_string_free(g_begin, TRUE);
	}

	if (g_end) {
		g_string_free(g_end, TRUE);
	}

	return ret;
}

static
int g_list_prepend_gstring(GList **list, const char *string)
{
	int ret = 0;
	GString *gs = g_string_new(string);

	BT_ASSERT(list);

	if (!gs) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto end;
	}

	*list = g_list_prepend(*list, gs);

end:
	return ret;
}

/*
 * Create `struct implicit_component_args` structures for each of the
 * source components we identified.  Add them to `component_args`.
 *
 * `non_opts` is an array of the non-option arguments passed on the command
 * line.
 *
 * `non_opt_params` is an array where each element is an array of
 * strings containing all the arguments to `--params` that apply to the
 * non-option argument at the same index.  For example, if, for a
 * non-option argument, the following `--params` options applied:
 *
 *     --params=a=2 --params=b=3,c=4
 *
 * its entry in `non_opt_params` would contain
 *
 *     ["a=2", "b=3,c=4"]
 */

static
int create_implicit_component_args_from_auto_discovered_sources(
		const struct auto_source_discovery *auto_disc,
		const bt_value *non_opts,
		const bt_value *non_opt_params,
		const bt_value *non_opt_loglevels,
		GPtrArray *component_args)
{
	gchar *cc_name = NULL;
	struct implicit_component_args *comp = NULL;
	int status;
	guint i, len;

	len = auto_disc->results->len;

	for (i = 0; i < len; i++) {
		struct auto_source_discovery_result *res =
			g_ptr_array_index(auto_disc->results, i);
		uint64_t orig_indices_i, orig_indices_count;

		g_free(cc_name);
		cc_name = g_strdup_printf("source.%s.%s", res->plugin_name, res->source_cc_name);
		if (!cc_name) {
			BT_CLI_LOGE_APPEND_CAUSE_OOM();
			goto error;
		}

		comp = create_implicit_component_args(cc_name);
		if (!comp) {
			goto error;
		}

		/*
		 * Append parameters and log levels of all the
		 * non-option arguments that contributed to this
		 * component instance coming into existence.
		 */
		orig_indices_count = bt_value_array_get_length(res->original_input_indices);
		for (orig_indices_i = 0; orig_indices_i < orig_indices_count; orig_indices_i++) {
			const bt_value *orig_idx_value =
				bt_value_array_borrow_element_by_index(
					res->original_input_indices, orig_indices_i);
			uint64_t orig_idx = bt_value_integer_unsigned_get(orig_idx_value);
			const bt_value *params_array =
				bt_value_array_borrow_element_by_index_const(
					non_opt_params, orig_idx);
			uint64_t params_i, params_count;
			const bt_value *loglevel_value;

			params_count = bt_value_array_get_length(params_array);
			for (params_i = 0; params_i < params_count; params_i++) {
				const bt_value *params_value =
					bt_value_array_borrow_element_by_index_const(
						params_array, params_i);
				const char *params = bt_value_string_get(params_value);
				bt_value_array_append_element_status append_status;

				append_status = bt_value_array_append_string_element(
					comp->extra_params, "--params");
				if (append_status != BT_VALUE_ARRAY_APPEND_ELEMENT_STATUS_OK) {
					BT_CLI_LOGE_APPEND_CAUSE("Failed to append array element.");
					goto error;
				}

				append_status = bt_value_array_append_string_element(
					comp->extra_params, params);
				if (append_status != BT_VALUE_ARRAY_APPEND_ELEMENT_STATUS_OK) {
					BT_CLI_LOGE_APPEND_CAUSE("Failed to append array element.");
					goto error;
				}
			}

			loglevel_value = bt_value_array_borrow_element_by_index_const(
				non_opt_loglevels, orig_idx);
			if (bt_value_get_type(loglevel_value) == BT_VALUE_TYPE_STRING) {
				const char *loglevel = bt_value_string_get(loglevel_value);
				bt_value_array_append_element_status append_status;

				append_status = bt_value_array_append_string_element(
					comp->extra_params, "--log-level");
				if (append_status != BT_VALUE_ARRAY_APPEND_ELEMENT_STATUS_OK) {
					BT_CLI_LOGE_APPEND_CAUSE("Failed to append array element.");
					goto error;
				}

				append_status = bt_value_array_append_string_element(
					comp->extra_params, loglevel);
				if (append_status != BT_VALUE_ARRAY_APPEND_ELEMENT_STATUS_OK) {
					BT_CLI_LOGE_APPEND_CAUSE("Failed to append array element.");
					goto error;
				}
			}
		}

		/*
		 * If single input and a src.ctf.fs component, provide the
		 * relative path from the path passed on the command line to the
		 * found trace.
		 */
		if (bt_value_array_get_length(res->inputs) == 1 &&
				strcmp(res->plugin_name, "ctf") == 0 &&
				strcmp(res->source_cc_name, "fs") == 0) {
			const bt_value *orig_idx_value =
				bt_value_array_borrow_element_by_index(
					res->original_input_indices, 0);
			uint64_t orig_idx = bt_value_integer_unsigned_get(orig_idx_value);
			const bt_value *non_opt_value =
				bt_value_array_borrow_element_by_index_const(
					non_opts, orig_idx);
			const char *non_opt = bt_value_string_get(non_opt_value);
			const bt_value *input_value =
				bt_value_array_borrow_element_by_index_const(
					res->inputs, 0);
			const char *input = bt_value_string_get(input_value);

			BT_ASSERT(orig_indices_count == 1);
			BT_ASSERT(g_str_has_prefix(input, non_opt));

			input += strlen(non_opt);

			while (G_IS_DIR_SEPARATOR(*input)) {
				input++;
			}

			if (strlen(input) > 0) {
				append_string_parameter_to_args(comp->extra_params,
					"trace-name", input);
			}
		}

		status = append_parameter_to_args(comp->extra_params, "inputs", res->inputs);
		if (status != 0) {
			goto error;
		}

		g_ptr_array_add(component_args, comp);
		comp = NULL;
	}

	status = 0;
	goto end;

error:
	status = -1;

end:
	g_free(cc_name);

	if (comp) {
		destroy_implicit_component_args(comp);
	}

	return status;
}

/*
 * As we iterate the arguments to the convert command, this tracks what is the
 * type of the current item, to which some contextual options (e.g. --params)
 * apply to.
 */
enum convert_current_item_type {
	/* There is no current item. */
	CONVERT_CURRENT_ITEM_TYPE_NONE,

	/* Current item is a component. */
	CONVERT_CURRENT_ITEM_TYPE_COMPONENT,

	/* Current item is a non-option argument. */
	CONVERT_CURRENT_ITEM_TYPE_NON_OPT,
};

/*
 * Creates a Babeltrace config object from the arguments of a convert
 * command.
 */
static
enum bt_config_cli_args_status bt_config_convert_from_args(int argc,
		const char *argv[], struct bt_config **cfg_out,
		const bt_value *plugin_paths,
		int *default_log_level, const bt_interrupter *interrupter,
		unsigned int consumed_args)
{
	enum bt_config_cli_args_status status;
	enum convert_current_item_type current_item_type =
		CONVERT_CURRENT_ITEM_TYPE_NONE;
	int ret;
	struct bt_config *cfg = NULL;
	bool got_input_format_opt = false;
	bool got_output_format_opt = false;
	bool trimmer_has_begin = false;
	bool trimmer_has_end = false;
	bool stream_intersection_mode = false;
	bool print_run_args = false;
	bool print_run_args_0 = false;
	bool print_ctf_metadata = false;
	bt_value *run_args = NULL;
	bt_value *all_names = NULL;
	GList *source_names = NULL;
	GList *filter_names = NULL;
	GList *sink_names = NULL;
	bt_value *non_opts = NULL;
	bt_value *non_opt_params = NULL;
	bt_value *non_opt_loglevels = NULL;
	struct implicit_component_args implicit_ctf_output_args = { 0 };
	struct implicit_component_args implicit_lttng_live_args = { 0 };
	struct implicit_component_args implicit_dummy_args = { 0 };
	struct implicit_component_args implicit_text_args = { 0 };
	struct implicit_component_args implicit_debug_info_args = { 0 };
	struct implicit_component_args implicit_muxer_args = { 0 };
	struct implicit_component_args implicit_trimmer_args = { 0 };
	char error_buf[256] = { 0 };
	size_t i;
	struct bt_common_lttng_live_url_parts lttng_live_url_parts = { 0 };
	char *output = NULL;
	struct auto_source_discovery auto_disc = { NULL };
	GString *auto_disc_comp_name = NULL;
	struct argpar_iter *argpar_iter = NULL;
	const struct argpar_item *argpar_item = NULL;
	GString *name_gstr = NULL;
	GString *component_arg_for_run = NULL;
	bt_value *live_inputs_array_val = NULL;

	/*
	 * Array of `struct implicit_component_args *` created for the sources
	 * we have auto-discovered.
	 */
	GPtrArray *discovered_source_args = NULL;

	/*
	 * If set, restrict automatic source discovery to this component class
	 * of this plugin.
	 */
	const char *auto_source_discovery_restrict_plugin_name = NULL;
	const char *auto_source_discovery_restrict_component_class_name = NULL;

	bool ctf_fs_source_force_clock_class_unix_epoch_origin = false;
	gchar *ctf_fs_source_clock_class_offset_arg = NULL;
	gchar *ctf_fs_source_clock_class_offset_ns_arg = NULL;

	if (argc < 1) {
		print_convert_usage(stdout);
		status = BT_CONFIG_CLI_ARGS_STATUS_INFO_ONLY;
		goto end;
	}

	if (init_implicit_component_args(&implicit_ctf_output_args,
			"sink.ctf.fs", false)) {
		goto error;
	}

	if (init_implicit_component_args(&implicit_lttng_live_args,
			"source.ctf.lttng-live", false)) {
		goto error;
	}

	if (init_implicit_component_args(&implicit_text_args,
			"sink.text.pretty", false)) {
		goto error;
	}

	if (init_implicit_component_args(&implicit_dummy_args,
			"sink.utils.dummy", false)) {
		goto error;
	}

	if (init_implicit_component_args(&implicit_debug_info_args,
			"filter.lttng-utils.debug-info", false)) {
		goto error;
	}

	if (init_implicit_component_args(&implicit_muxer_args,
			"filter.utils.muxer", true)) {
		goto error;
	}

	if (init_implicit_component_args(&implicit_trimmer_args,
			"filter.utils.trimmer", false)) {
		goto error;
	}

	all_names = bt_value_map_create();
	if (!all_names) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	run_args = bt_value_array_create();
	if (!run_args) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	component_arg_for_run = g_string_new(NULL);
	if (!component_arg_for_run) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	non_opts = bt_value_array_create();
	if (!non_opts) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	non_opt_params = bt_value_array_create();
	if (!non_opt_params) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	non_opt_loglevels = bt_value_array_create();
	if (!non_opt_loglevels) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	if (auto_source_discovery_init(&auto_disc) != 0) {
		goto error;
	}

	discovered_source_args =
		g_ptr_array_new_with_free_func((GDestroyNotify) destroy_implicit_component_args);
	if (!discovered_source_args) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	auto_disc_comp_name = g_string_new(NULL);
	if (!auto_disc_comp_name) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	/*
	 * First pass: collect all arguments which need to be passed
	 * as is to the run command. This pass can also add --name
	 * arguments if needed to automatically name unnamed component
	 * instances.
	 */
	argpar_iter = argpar_iter_create(argc, argv, convert_options);
	if (!argpar_iter) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	while (true) {
		enum parse_next_item_status parse_status;
		char *name = NULL;
		char *plugin_name = NULL;
		char *comp_cls_name = NULL;

		parse_status = parse_next_item(argpar_iter, &argpar_item, argv, "convert",
			consumed_args);
		if (parse_status == PARSE_NEXT_ITEM_STATUS_ERROR) {
			goto error;
		} else if (parse_status == PARSE_NEXT_ITEM_STATUS_END) {
			break;
		}

		if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) {
			const struct argpar_opt_descr *opt_descr =
				argpar_item_opt_descr(argpar_item);
			const char *arg = argpar_item_opt_arg(argpar_item);

			switch (opt_descr->id) {
			case OPT_HELP:
				print_convert_usage(stdout);
				status = BT_CONFIG_CLI_ARGS_STATUS_INFO_ONLY;
				goto end;
			case OPT_COMPONENT:
			{
				bt_component_class_type type;

				current_item_type = CONVERT_CURRENT_ITEM_TYPE_COMPONENT;

				/* Parse the argument */
				plugin_comp_cls_names(arg, &name, &plugin_name,
					&comp_cls_name, &type);
				if (!plugin_name || !comp_cls_name) {
					BT_CLI_LOGE_APPEND_CAUSE(
						"Invalid format for --component option's argument:\n    %s",
						arg);
					goto error;
				}

				if (name) {
					/*
					 * Name was given by the user, verify it isn't
					 * taken.
					 */
					if (bt_value_map_has_entry(all_names, name)) {
						BT_CLI_LOGE_APPEND_CAUSE(
							"Duplicate component instance name:\n    %s",
							name);
						goto error;
					}

					name_gstr = g_string_new(name);
					if (!name_gstr) {
						BT_CLI_LOGE_APPEND_CAUSE_OOM();
						goto error;
					}

					g_string_assign(component_arg_for_run, arg);
				} else {
					/* Name not given by user, generate one. */
					name_gstr = get_component_auto_name(arg, all_names);
					if (!name_gstr) {
						goto error;
					}

					g_string_printf(component_arg_for_run, "%s:%s",
						name_gstr->str, arg);
				}

				if (bt_value_array_append_string_element(run_args,
						"--component")) {
					BT_CLI_LOGE_APPEND_CAUSE_OOM();
					goto error;
				}

				if (bt_value_array_append_string_element(run_args,
						component_arg_for_run->str)) {
					BT_CLI_LOGE_APPEND_CAUSE_OOM();
					goto error;
				}

				/*
				 * Remember this name globally, for the uniqueness of
				 * all component names.
				 */
				if (bt_value_map_insert_entry(all_names,
						name_gstr->str, bt_value_null)) {
					BT_CLI_LOGE_APPEND_CAUSE_OOM();
					goto error;
				}

				/*
				 * Remember this name specifically for the type of the
				 * component. This is to create connection arguments.
				 *
				 * The list takes ownership of `name_gstr`.
				 */
				switch (type) {
				case BT_COMPONENT_CLASS_TYPE_SOURCE:
					source_names = g_list_append(source_names, name_gstr);
					break;
				case BT_COMPONENT_CLASS_TYPE_FILTER:
					filter_names = g_list_append(filter_names, name_gstr);
					break;
				case BT_COMPONENT_CLASS_TYPE_SINK:
					sink_names = g_list_append(sink_names, name_gstr);
					break;
				default:
					bt_common_abort();
				}
				name_gstr = NULL;

				free(name);
				free(plugin_name);
				free(comp_cls_name);
				name = NULL;
				plugin_name = NULL;
				comp_cls_name = NULL;
				break;
			}
			case OPT_PARAMS:
				if (current_item_type == CONVERT_CURRENT_ITEM_TYPE_COMPONENT) {
					/*
					 * The current item is a component (--component option),
					 * pass it directly to the run args.
					 */
					if (bt_value_array_append_string_element(run_args,
							"--params")) {
						BT_CLI_LOGE_APPEND_CAUSE_OOM();
						goto error;
					}

					if (bt_value_array_append_string_element(run_args, arg)) {
						BT_CLI_LOGE_APPEND_CAUSE_OOM();
						goto error;
					}
				} else if (current_item_type == CONVERT_CURRENT_ITEM_TYPE_NON_OPT) {
					/*
					 * The current item is a
					 * non-option argument, record
					 * it in `non_opt_params`.
					 */
					bt_value *array;
					bt_value_array_append_element_status append_element_status;
					uint64_t idx = bt_value_array_get_length(non_opt_params) - 1;

					array = bt_value_array_borrow_element_by_index(non_opt_params, idx);

					append_element_status = bt_value_array_append_string_element(array, arg);
					if (append_element_status != BT_VALUE_ARRAY_APPEND_ELEMENT_STATUS_OK) {
						BT_CLI_LOGE_APPEND_CAUSE_OOM();
						goto error;
					}
				} else {
					BT_CLI_LOGE_APPEND_CAUSE(
						"No current component (--component option) or non-option argument of which to set parameters:\n    %s",
						arg);
					goto error;
				}
				break;
			case OPT_LOG_LEVEL:
				if (current_item_type == CONVERT_CURRENT_ITEM_TYPE_COMPONENT) {
					if (bt_value_array_append_string_element(run_args, "--log-level")) {
						BT_CLI_LOGE_APPEND_CAUSE_OOM();
						goto error;
					}

					if (bt_value_array_append_string_element(run_args, arg)) {
						BT_CLI_LOGE_APPEND_CAUSE_OOM();
						goto error;
					}
				} else if (current_item_type == CONVERT_CURRENT_ITEM_TYPE_NON_OPT) {
					uint64_t idx = bt_value_array_get_length(non_opt_loglevels) - 1;
					enum bt_value_array_set_element_by_index_status set_element_status;
					bt_value *log_level_str_value;

					log_level_str_value = bt_value_string_create_init(arg);
					if (!log_level_str_value) {
						BT_CLI_LOGE_APPEND_CAUSE_OOM();
						goto error;
					}

					set_element_status =
						bt_value_array_set_element_by_index(non_opt_loglevels,
							idx, log_level_str_value);
					bt_value_put_ref(log_level_str_value);
					if (set_element_status != BT_VALUE_ARRAY_SET_ELEMENT_BY_INDEX_STATUS_OK) {
						BT_CLI_LOGE_APPEND_CAUSE_OOM();
						goto error;
					}
				} else {
					BT_CLI_LOGE_APPEND_CAUSE(
						"No current component (--component option) or non-option argument to assign a log level to:\n    %s",
						arg);
					goto error;
				}

				break;
			case OPT_RETRY_DURATION:
				if (bt_value_array_append_string_element(run_args,
						"--retry-duration")) {
					BT_CLI_LOGE_APPEND_CAUSE_OOM();
					goto error;
				}

				if (bt_value_array_append_string_element(run_args, arg)) {
					BT_CLI_LOGE_APPEND_CAUSE_OOM();
					goto error;
				}
				break;
			case OPT_BEGIN:
			case OPT_CLOCK_CYCLES:
			case OPT_CLOCK_DATE:
			case OPT_CLOCK_FORCE_CORRELATE:
			case OPT_CLOCK_GMT:
			case OPT_CLOCK_OFFSET:
			case OPT_CLOCK_OFFSET_NS:
			case OPT_CLOCK_SECONDS:
			case OPT_COLOR:
			case OPT_DEBUG:
			case OPT_DEBUG_INFO:
			case OPT_DEBUG_INFO_DIR:
			case OPT_DEBUG_INFO_FULL_PATH:
			case OPT_DEBUG_INFO_TARGET_PREFIX:
			case OPT_END:
			case OPT_FIELDS:
			case OPT_INPUT_FORMAT:
			case OPT_NAMES:
			case OPT_NO_DELTA:
			case OPT_OUTPUT_FORMAT:
			case OPT_OUTPUT:
			case OPT_RUN_ARGS:
			case OPT_RUN_ARGS_0:
			case OPT_STREAM_INTERSECTION:
			case OPT_TIMERANGE:
			case OPT_VERBOSE:
				/* Ignore in this pass */
				break;
			default:
				bt_common_abort();
			}
		} else {
			const char *arg = argpar_item_non_opt_arg(argpar_item);
			bt_value_array_append_element_status append_status;

			current_item_type = CONVERT_CURRENT_ITEM_TYPE_NON_OPT;

			append_status = bt_value_array_append_string_element(non_opts, arg);
			if (append_status != BT_VALUE_ARRAY_APPEND_ELEMENT_STATUS_OK) {
				BT_CLI_LOGE_APPEND_CAUSE_OOM();
				goto error;
			}

			append_status = bt_value_array_append_empty_array_element(
				non_opt_params, NULL);
			if (append_status != BT_VALUE_ARRAY_APPEND_ELEMENT_STATUS_OK) {
				BT_CLI_LOGE_APPEND_CAUSE_OOM();
				goto error;
			}

			append_status = bt_value_array_append_element(non_opt_loglevels, bt_value_null);
			if (append_status != BT_VALUE_ARRAY_APPEND_ELEMENT_STATUS_OK) {
				BT_CLI_LOGE_APPEND_CAUSE_OOM();
				goto error;
			}
		}
	}

	/*
	 * Second pass: transform the convert-specific options and
	 * arguments into implicit component instances for the run
	 * command.
	 */
	argpar_iter_destroy(argpar_iter);
	argpar_iter = argpar_iter_create(argc, argv, convert_options);
	if (!argpar_iter) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	while (true) {
		enum parse_next_item_status parse_status;
		const struct argpar_opt_descr *opt_descr;
		const char *arg;

		parse_status = parse_next_item(argpar_iter, &argpar_item, argv, "convert",
			consumed_args);
		if (parse_status == PARSE_NEXT_ITEM_STATUS_ERROR) {
			goto error;
		} else if (parse_status == PARSE_NEXT_ITEM_STATUS_END) {
			break;
		}

		if (argpar_item_type(argpar_item) != ARGPAR_ITEM_TYPE_OPT) {
			continue;
		}

		opt_descr = argpar_item_opt_descr(argpar_item);
		arg = argpar_item_opt_arg(argpar_item);

		switch (opt_descr->id) {
		case OPT_BEGIN:
			if (trimmer_has_begin) {
				BT_CLI_LOGE_APPEND_CAUSE("At --begin option: --begin or --timerange option already specified\n    %s\n",
					arg);
				goto error;
			}

			trimmer_has_begin = true;
			ret = append_implicit_component_extra_param(
				&implicit_trimmer_args, "begin", arg);
			implicit_trimmer_args.exists = true;
			if (ret) {
				goto error;
			}
			break;
		case OPT_END:
			if (trimmer_has_end) {
				BT_CLI_LOGE_APPEND_CAUSE("At --end option: --end or --timerange option already specified\n    %s\n",
					arg);
				goto error;
			}

			trimmer_has_end = true;
			ret = append_implicit_component_extra_param(
				&implicit_trimmer_args, "end", arg);
			implicit_trimmer_args.exists = true;
			if (ret) {
				goto error;
			}
			break;
		case OPT_TIMERANGE:
		{
			char *begin;
			char *end;

			if (trimmer_has_begin || trimmer_has_end) {
				BT_CLI_LOGE_APPEND_CAUSE("At --timerange option: --begin, --end, or --timerange option already specified\n    %s\n",
					arg);
				goto error;
			}

			ret = split_timerange(arg, &begin, &end);
			if (ret) {
				BT_CLI_LOGE_APPEND_CAUSE("Invalid --timerange option's argument: expecting BEGIN,END or [BEGIN,END]:\n    %s",
					arg);
				goto error;
			}

			ret = append_implicit_component_extra_param(
				&implicit_trimmer_args, "begin", begin);
			ret |= append_implicit_component_extra_param(
				&implicit_trimmer_args, "end", end);
			implicit_trimmer_args.exists = true;
			free(begin);
			free(end);
			if (ret) {
				goto error;
			}
			break;
		}
		case OPT_CLOCK_CYCLES:
			append_implicit_component_param(
				&implicit_text_args, "clock-cycles", "yes");
			implicit_text_args.exists = true;
			break;
		case OPT_CLOCK_DATE:
			append_implicit_component_param(
				&implicit_text_args, "clock-date", "yes");
			implicit_text_args.exists = true;
			break;
		case OPT_CLOCK_FORCE_CORRELATE:
			ctf_fs_source_force_clock_class_unix_epoch_origin = true;
			break;
		case OPT_CLOCK_GMT:
			append_implicit_component_param(
				&implicit_text_args, "clock-gmt", "yes");
			append_implicit_component_param(
				&implicit_trimmer_args, "gmt", "yes");
			implicit_text_args.exists = true;
			break;
		case OPT_CLOCK_OFFSET:
			if (ctf_fs_source_clock_class_offset_arg) {
				BT_CLI_LOGE_APPEND_CAUSE("Duplicate --clock-offset option\n");
				goto error;
			}

			ctf_fs_source_clock_class_offset_arg = g_strdup(arg);
			if (!ctf_fs_source_clock_class_offset_arg) {
				BT_CLI_LOGE_APPEND_CAUSE_OOM();
				goto error;
			}
			break;
		case OPT_CLOCK_OFFSET_NS:
			if (ctf_fs_source_clock_class_offset_ns_arg) {
				BT_CLI_LOGE_APPEND_CAUSE("Duplicate --clock-offset-ns option\n");
				goto error;
			}

			ctf_fs_source_clock_class_offset_ns_arg = g_strdup(arg);
			if (!ctf_fs_source_clock_class_offset_ns_arg) {
				BT_CLI_LOGE_APPEND_CAUSE_OOM();
				goto error;
			}
			break;
		case OPT_CLOCK_SECONDS:
			append_implicit_component_param(
				&implicit_text_args, "clock-seconds", "yes");
			implicit_text_args.exists = true;
			break;
		case OPT_COLOR:
			implicit_text_args.exists = true;
			ret = append_implicit_component_extra_param(
				&implicit_text_args, "color", arg);
			if (ret) {
				goto error;
			}
			break;
		case OPT_DEBUG_INFO:
			implicit_debug_info_args.exists = true;
			break;
		case OPT_DEBUG_INFO_DIR:
			implicit_debug_info_args.exists = true;
			ret = append_implicit_component_extra_param(
				&implicit_debug_info_args, "debug-info-dir", arg);
			if (ret) {
				goto error;
			}
			break;
		case OPT_DEBUG_INFO_FULL_PATH:
			implicit_debug_info_args.exists = true;
			append_implicit_component_param(
				&implicit_debug_info_args, "full-path", "yes");
			break;
		case OPT_DEBUG_INFO_TARGET_PREFIX:
			implicit_debug_info_args.exists = true;
			ret = append_implicit_component_extra_param(
				&implicit_debug_info_args,
				"target-prefix", arg);
			if (ret) {
				goto error;
			}
			break;
		case OPT_FIELDS:
		{
			bt_value *fields = fields_from_arg(arg);

			if (!fields) {
				goto error;
			}

			implicit_text_args.exists = true;
			ret = insert_flat_params_from_array(
				implicit_text_args.params_arg,
				fields, "field");
			bt_value_put_ref(fields);
			if (ret) {
				goto error;
			}
			break;
		}
		case OPT_NAMES:
		{
			bt_value *names = names_from_arg(arg);

			if (!names) {
				goto error;
			}

			implicit_text_args.exists = true;
			ret = insert_flat_params_from_array(
				implicit_text_args.params_arg,
				names, "name");
			bt_value_put_ref(names);
			if (ret) {
				goto error;
			}
			break;
		}
		case OPT_NO_DELTA:
			append_implicit_component_param(
				&implicit_text_args, "no-delta", "yes");
			implicit_text_args.exists = true;
			break;
		case OPT_INPUT_FORMAT:
			if (got_input_format_opt) {
				BT_CLI_LOGE_APPEND_CAUSE("Duplicate --input-format option.");
				goto error;
			}

			got_input_format_opt = true;

			if (strcmp(arg, "ctf") == 0) {
				auto_source_discovery_restrict_plugin_name = "ctf";
				auto_source_discovery_restrict_component_class_name = "fs";
			} else if (strcmp(arg, "lttng-live") == 0) {
				auto_source_discovery_restrict_plugin_name = "ctf";
				auto_source_discovery_restrict_component_class_name = "lttng-live";
				implicit_lttng_live_args.exists = true;
			} else {
				BT_CLI_LOGE_APPEND_CAUSE("Unknown legacy input format:\n    %s",
					arg);
				goto error;
			}
			break;
		case OPT_OUTPUT_FORMAT:
			if (got_output_format_opt) {
				BT_CLI_LOGE_APPEND_CAUSE("Duplicate --output-format option.");
				goto error;
			}

			got_output_format_opt = true;

			if (strcmp(arg, "text") == 0) {
				implicit_text_args.exists = true;
			} else if (strcmp(arg, "ctf") == 0) {
				implicit_ctf_output_args.exists = true;
			} else if (strcmp(arg, "dummy") == 0) {
				implicit_dummy_args.exists = true;
			} else if (strcmp(arg, "ctf-metadata") == 0) {
				print_ctf_metadata = true;
			} else {
				BT_CLI_LOGE_APPEND_CAUSE("Unknown legacy output format:\n    %s",
					arg);
				goto error;
			}
			break;
		case OPT_OUTPUT:
			if (output) {
				BT_CLI_LOGE_APPEND_CAUSE("Duplicate --output option");
				goto error;
			}

			output = strdup(arg);
			if (!output) {
				BT_CLI_LOGE_APPEND_CAUSE_OOM();
				goto error;
			}
			break;
		case OPT_RUN_ARGS:
			if (print_run_args_0) {
				BT_CLI_LOGE_APPEND_CAUSE("Cannot specify --run-args and --run-args-0.");
				goto error;
			}

			print_run_args = true;
			break;
		case OPT_RUN_ARGS_0:
			if (print_run_args) {
				BT_CLI_LOGE_APPEND_CAUSE("Cannot specify --run-args and --run-args-0.");
				goto error;
			}

			print_run_args_0 = true;
			break;
		case OPT_STREAM_INTERSECTION:
			/*
			 * Applies to all traces implementing the
			 * babeltrace.trace-infos query.
			 */
			stream_intersection_mode = true;
			break;
		case OPT_VERBOSE:
			*default_log_level =
				logging_level_min(*default_log_level, BT_LOG_INFO);
			break;
		case OPT_DEBUG:
			*default_log_level =
				logging_level_min(*default_log_level, BT_LOG_TRACE);
			break;
		case OPT_COMPONENT:
		case OPT_HELP:
		case OPT_LOG_LEVEL:
		case OPT_OMIT_HOME_PLUGIN_PATH:
		case OPT_OMIT_SYSTEM_PLUGIN_PATH:
		case OPT_PARAMS:
		case OPT_PLUGIN_PATH:
		case OPT_RETRY_DURATION:
			/* Ignore in this pass */
			break;
		default:
			bt_common_abort();
		}
	}

	set_auto_log_levels(default_log_level);

	/*
	 * Legacy behaviour: --verbose used to make the `text` output
	 * format print more information. --verbose is now equivalent to
	 * the INFO log level, which is why we compare to `BT_LOG_INFO`
	 * here.
	 */
	if (*default_log_level == BT_LOG_INFO) {
		append_implicit_component_param(&implicit_text_args,
			"verbose", "yes");
	}

	/* Print CTF metadata or print LTTng live sessions */
	if (print_ctf_metadata) {
		const bt_value *bt_val_non_opt;

		if (bt_value_array_is_empty(non_opts)) {
			BT_CLI_LOGE_APPEND_CAUSE("--output-format=ctf-metadata specified without a path.");
			goto error;
		}

		if (bt_value_array_get_length(non_opts) > 1) {
			BT_CLI_LOGE_APPEND_CAUSE("Too many paths specified for --output-format=ctf-metadata.");
			goto error;
		}

		status = bt_config_print_ctf_metadata_create(plugin_paths, &cfg);
		if (status != BT_CONFIG_CLI_ARGS_STATUS_OK) {
			goto end;
		}

		bt_val_non_opt = bt_value_array_borrow_element_by_index_const(non_opts, 0);
		g_string_assign(cfg->cmd_data.print_ctf_metadata.path,
				bt_value_string_get(bt_val_non_opt));

		if (output) {
			g_string_assign(
				cfg->cmd_data.print_ctf_metadata.output_path,
				output);
		}

		BT_OBJECT_MOVE_REF(*cfg_out, cfg);
		goto end;
	}

	/*
	 * If -o ctf was specified, make sure an output path (--output)
	 * was also specified. --output does not imply -o ctf because
	 * it's also used for the default, implicit -o text if -o ctf
	 * is not specified.
	 */
	if (implicit_ctf_output_args.exists) {
		if (!output) {
			BT_CLI_LOGE_APPEND_CAUSE("--output-format=ctf specified without --output (trace output path).");
			goto error;
		}

		/*
		 * At this point we know that -o ctf AND --output were
		 * specified. Make sure that no options were specified
		 * which would imply -o text because --output would be
		 * ambiguous in this case. For example, this is wrong:
		 *
		 *     babeltrace2 --names=all -o ctf --output=/tmp/path my-trace
		 *
		 * because --names=all implies -o text, and --output
		 * could apply to both the sink.text.pretty and
		 * sink.ctf.fs implicit components.
		 */
		if (implicit_text_args.exists) {
			BT_CLI_LOGE_APPEND_CAUSE("Ambiguous --output option: --output-format=ctf specified but another option implies --output-format=text.");
			goto error;
		}
	}

	/*
	 * If -o dummy and -o ctf were not specified, and if there are
	 * no explicit sink components, then use an implicit
	 * `sink.text.pretty` component.
	 */
	if (!implicit_dummy_args.exists && !implicit_ctf_output_args.exists &&
			!sink_names) {
		implicit_text_args.exists = true;
	}

	/*
	 * Set implicit `sink.text.pretty` or `sink.ctf.fs` component's
	 * `path` parameter if --output was specified.
	 */
	if (output) {
		if (implicit_text_args.exists) {
			append_implicit_component_extra_param(&implicit_text_args,
				"path", output);
		} else if (implicit_ctf_output_args.exists) {
			append_implicit_component_extra_param(&implicit_ctf_output_args,
				"path", output);
		}
	}

	/* Decide where the non-option argument(s) go */
	if (bt_value_array_get_length(non_opts) > 0) {
		if (implicit_lttng_live_args.exists) {
			const bt_value *bt_val_non_opt;

			if (bt_value_array_get_length(non_opts) > 1) {
				BT_CLI_LOGE_APPEND_CAUSE("Too many URLs specified for --input-format=lttng-live.");
				goto error;
			}

			bt_val_non_opt = bt_value_array_borrow_element_by_index_const(non_opts, 0);
			lttng_live_url_parts =
				bt_common_parse_lttng_live_url(bt_value_string_get(bt_val_non_opt),
					error_buf, sizeof(error_buf));
			if (!lttng_live_url_parts.proto) {
				BT_CLI_LOGE_APPEND_CAUSE("Invalid LTTng live URL format: %s.",
					error_buf);
				goto error;
			}

			if (!lttng_live_url_parts.session_name) {
				/* Print LTTng live sessions */
				status = bt_config_print_lttng_live_sessions_create(
					plugin_paths, &cfg);
				if (status != BT_CONFIG_CLI_ARGS_STATUS_OK) {
					goto end;
				}

				g_string_assign(cfg->cmd_data.print_lttng_live_sessions.url,
					bt_value_string_get(bt_val_non_opt));

				if (output) {
					g_string_assign(
						cfg->cmd_data.print_lttng_live_sessions.output_path,
						output);
				}

				BT_OBJECT_MOVE_REF(*cfg_out, cfg);
				goto end;
			}

			live_inputs_array_val = bt_value_array_create();
			if (!live_inputs_array_val) {
				BT_CLI_LOGE_APPEND_CAUSE_OOM();
				goto error;
			}

			if (bt_value_array_append_string_element(
					live_inputs_array_val,
					bt_value_string_get(bt_val_non_opt))) {
				BT_CLI_LOGE_APPEND_CAUSE_OOM();
				goto error;
			}

			ret = append_parameter_to_args(
				implicit_lttng_live_args.extra_params,
				"inputs", live_inputs_array_val);
			if (ret) {
				goto error;
			}

			ret = append_implicit_component_extra_param(
				&implicit_lttng_live_args,
				"session-not-found-action", "end");
			if (ret) {
				goto error;
			}
		} else {
			size_t plugin_count;
			const bt_plugin **plugins;
			const bt_plugin *plugin;
			auto_source_discovery_status auto_disc_status;

			ret = require_loaded_plugins(plugin_paths);
			if (ret != 0) {
				goto error;
			}

			if (auto_source_discovery_restrict_plugin_name) {
				plugin_count = 1;
				plugin = borrow_loaded_plugin_by_name(auto_source_discovery_restrict_plugin_name);
				plugins = &plugin;
			} else {
				plugin_count = get_loaded_plugins_count();
				plugins = borrow_loaded_plugins();
			}

			auto_disc_status = auto_discover_source_components(
				non_opts, plugins, plugin_count,
				auto_source_discovery_restrict_component_class_name,
				*default_log_level, &auto_disc, interrupter);

			if (auto_disc_status != AUTO_SOURCE_DISCOVERY_STATUS_OK) {
				if (auto_disc_status == AUTO_SOURCE_DISCOVERY_STATUS_INTERRUPTED) {
					BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_UNKNOWN(
						"Babeltrace CLI", "Automatic source discovery interrupted by the user");
				}
				goto error;
			}

			ret = create_implicit_component_args_from_auto_discovered_sources(
				&auto_disc, non_opts, non_opt_params, non_opt_loglevels,
				discovered_source_args);
			if (ret != 0) {
				goto error;
			}
		}
	}


	/*
	 * If --clock-force-correlated was given, apply it to any src.ctf.fs
	 * component.
	 */
	if (ctf_fs_source_force_clock_class_unix_epoch_origin) {
		int n;

		n = append_multiple_implicit_components_param(
			discovered_source_args, "source.ctf.fs", "force-clock-class-origin-unix-epoch",
			"yes");
		if (n == 0) {
			BT_CLI_LOGE_APPEND_CAUSE("--clock-force-correlate specified, but no source.ctf.fs component instantiated.");
			goto error;
		}
	}

	/* If --clock-offset was given, apply it to any src.ctf.fs component. */
	if (ctf_fs_source_clock_class_offset_arg) {
		int n;

		n = append_multiple_implicit_components_param(
			discovered_source_args, "source.ctf.fs", "clock-class-offset-s",
			ctf_fs_source_clock_class_offset_arg);

		if (n == 0) {
			BT_CLI_LOGE_APPEND_CAUSE("--clock-offset specified, but no source.ctf.fs component instantiated.");
			goto error;
		}
	}

	/* If --clock-offset-ns was given, apply it to any src.ctf.fs component. */
	if (ctf_fs_source_clock_class_offset_ns_arg) {
		int n;

		n = append_multiple_implicit_components_param(
			discovered_source_args, "source.ctf.fs", "clock-class-offset-ns",
			ctf_fs_source_clock_class_offset_ns_arg);

		if (n == 0) {
			BT_CLI_LOGE_APPEND_CAUSE("--clock-offset-ns specified, but no source.ctf.fs component instantiated.");
			goto error;
		}
	}

	/*
	 * If the implicit `source.ctf.lttng-live` component exists,
	 * make sure there's at least one non-option argument (which is
	 * the URL).
	 */
	if (implicit_lttng_live_args.exists && bt_value_array_is_empty(non_opts)) {
		BT_CLI_LOGE_APPEND_CAUSE("Missing URL for implicit `%s` component.",
			implicit_lttng_live_args.comp_arg->str);
		goto error;
	}

	/* Assign names to implicit components */
	for (i = 0; i < discovered_source_args->len; i++) {
		struct implicit_component_args *args;
		int j;

		args = discovered_source_args->pdata[i];

		g_string_printf(auto_disc_comp_name, "auto-disc-%s", args->comp_arg->str);

		/* Give it a name like `auto-disc-src-ctf-fs`. */
		for (j = 0; j < auto_disc_comp_name->len; j++) {
			if (auto_disc_comp_name->str[j] == '.') {
				auto_disc_comp_name->str[j] = '-';
			}
		}

		ret = assign_name_to_implicit_component(args,
			auto_disc_comp_name->str, all_names, &source_names, true);
		if (ret) {
			goto error;
		}
	}

	ret = assign_name_to_implicit_component(&implicit_lttng_live_args,
		"lttng-live", all_names, &source_names, true);
	if (ret) {
		goto error;
	}

	ret = assign_name_to_implicit_component(&implicit_text_args,
		"pretty", all_names, &sink_names, true);
	if (ret) {
		goto error;
	}

	ret = assign_name_to_implicit_component(&implicit_ctf_output_args,
		"sink-ctf-fs", all_names, &sink_names, true);
	if (ret) {
		goto error;
	}

	ret = assign_name_to_implicit_component(&implicit_dummy_args,
		"dummy", all_names, &sink_names, true);
	if (ret) {
		goto error;
	}

	ret = assign_name_to_implicit_component(&implicit_muxer_args,
		"muxer", all_names, NULL, false);
	if (ret) {
		goto error;
	}

	ret = assign_name_to_implicit_component(&implicit_trimmer_args,
		"trimmer", all_names, NULL, false);
	if (ret) {
		goto error;
	}

	ret = assign_name_to_implicit_component(&implicit_debug_info_args,
		"debug-info", all_names, NULL, false);
	if (ret) {
		goto error;
	}

	/* Make sure there's at least one source and one sink */
	if (!source_names) {
		BT_CLI_LOGE_APPEND_CAUSE("No source component.");
		goto error;
	}

	if (!sink_names) {
		BT_CLI_LOGE_APPEND_CAUSE("No sink component.");
		goto error;
	}

	/* Make sure there's a single sink component */
	if (g_list_length(sink_names) != 1) {
		BT_CLI_LOGE_APPEND_CAUSE(
			"More than one sink component specified.");
		goto error;
	}

	/*
	 * Prepend the muxer, the trimmer, and the debug info to the
	 * filter chain so that we have:
	 *
	 *     sources -> muxer -> [trimmer] -> [debug info] ->
	 *                [user filters] -> sinks
	 */
	if (implicit_debug_info_args.exists) {
		if (g_list_prepend_gstring(&filter_names,
				implicit_debug_info_args.name_arg->str)) {
			goto error;
		}
	}

	if (implicit_trimmer_args.exists) {
		if (g_list_prepend_gstring(&filter_names,
				implicit_trimmer_args.name_arg->str)) {
			goto error;
		}
	}

	if (g_list_prepend_gstring(&filter_names,
			implicit_muxer_args.name_arg->str)) {
		goto error;
	}

	/*
	 * Append the equivalent run arguments for the implicit
	 * components.
	 */
	for (i = 0; i < discovered_source_args->len; i++) {
		struct implicit_component_args *args =
			discovered_source_args->pdata[i];

		ret = append_run_args_for_implicit_component(args, run_args);
		if (ret) {
			goto error;
		}
	}

	ret = append_run_args_for_implicit_component(&implicit_lttng_live_args,
		run_args);
	if (ret) {
		goto error;
	}

	ret = append_run_args_for_implicit_component(&implicit_text_args,
		run_args);
	if (ret) {
		goto error;
	}

	ret = append_run_args_for_implicit_component(&implicit_ctf_output_args,
		run_args);
	if (ret) {
		goto error;
	}

	ret = append_run_args_for_implicit_component(&implicit_dummy_args,
		run_args);
	if (ret) {
		goto error;
	}

	ret = append_run_args_for_implicit_component(&implicit_muxer_args,
		run_args);
	if (ret) {
		goto error;
	}

	ret = append_run_args_for_implicit_component(&implicit_trimmer_args,
		run_args);
	if (ret) {
		goto error;
	}

	ret = append_run_args_for_implicit_component(&implicit_debug_info_args,
		run_args);
	if (ret) {
		goto error;
	}

	/* Auto-connect components */
	ret = convert_auto_connect(run_args, source_names, filter_names,
			sink_names);
	if (ret) {
		BT_CLI_LOGE_APPEND_CAUSE("Cannot auto-connect components.");
		goto error;
	}

	/*
	 * We have all the run command arguments now. Depending on
	 * --run-args, we pass this to the run command or print them
	 * here.
	 */
	if (print_run_args || print_run_args_0) {
		uint64_t args_idx, args_len;
		if (stream_intersection_mode) {
			BT_CLI_LOGE_APPEND_CAUSE("Cannot specify --stream-intersection with --run-args or --run-args-0.");
			goto error;
		}

		args_len = bt_value_array_get_length(run_args);
		for (args_idx = 0; args_idx < args_len; args_idx++) {
			const bt_value *arg_value =
				bt_value_array_borrow_element_by_index(run_args,
					args_idx);
			const char *arg;
			GString *quoted = NULL;
			const char *arg_to_print;

			arg = bt_value_string_get(arg_value);

			if (print_run_args) {
				quoted = bt_common_shell_quote(arg, true);
				if (!quoted) {
					goto error;
				}

				arg_to_print = quoted->str;
			} else {
				arg_to_print = arg;
			}

			printf("%s", arg_to_print);

			if (quoted) {
				g_string_free(quoted, TRUE);
			}

			if (args_idx < args_len - 1) {
				if (print_run_args) {
					putchar(' ');
				} else {
					putchar('\0');
				}
			}
		}

		status = BT_CONFIG_CLI_ARGS_STATUS_INFO_ONLY;
		goto end;
	}

	status = bt_config_run_from_args_array(run_args, &cfg,
		plugin_paths, *default_log_level);
	if (status != BT_CONFIG_CLI_ARGS_STATUS_OK) {
		goto end;
	}

	cfg->cmd_data.run.stream_intersection_mode = stream_intersection_mode;
	BT_OBJECT_MOVE_REF(*cfg_out, cfg);
	goto end;

error:
	status = BT_CONFIG_CLI_ARGS_STATUS_ERROR;

end:
	argpar_iter_destroy(argpar_iter);
	argpar_item_destroy(argpar_item);

	free(output);

	if (component_arg_for_run) {
		g_string_free(component_arg_for_run, TRUE);
	}

	if (name_gstr) {
		g_string_free(name_gstr, TRUE);
	}

	bt_value_put_ref(live_inputs_array_val);
	bt_value_put_ref(run_args);
	bt_value_put_ref(all_names);
	destroy_glist_of_gstring(source_names);
	destroy_glist_of_gstring(filter_names);
	destroy_glist_of_gstring(sink_names);
	bt_value_put_ref(non_opt_params);
	bt_value_put_ref(non_opt_loglevels);
	bt_value_put_ref(non_opts);
	finalize_implicit_component_args(&implicit_ctf_output_args);
	finalize_implicit_component_args(&implicit_lttng_live_args);
	finalize_implicit_component_args(&implicit_dummy_args);
	finalize_implicit_component_args(&implicit_text_args);
	finalize_implicit_component_args(&implicit_debug_info_args);
	finalize_implicit_component_args(&implicit_muxer_args);
	finalize_implicit_component_args(&implicit_trimmer_args);
	bt_common_destroy_lttng_live_url_parts(&lttng_live_url_parts);
	auto_source_discovery_fini(&auto_disc);

	if (discovered_source_args) {
		g_ptr_array_free(discovered_source_args, TRUE);
	}

	g_free(ctf_fs_source_clock_class_offset_arg);
	g_free(ctf_fs_source_clock_class_offset_ns_arg);

	if (auto_disc_comp_name) {
		g_string_free(auto_disc_comp_name, TRUE);
	}

	bt_object_put_ref(cfg);

	return status;
}

/*
 * Prints the Babeltrace 2.x general usage.
 */
static
void print_gen_usage(FILE *fp)
{
	fprintf(fp, "Usage: babeltrace2 [GENERAL OPTIONS] [COMMAND] [COMMAND ARGUMENTS]\n");
	fprintf(fp, "\n");
	fprintf(fp, "General options:\n");
	fprintf(fp, "\n");
	fprintf(fp, "  -d, --debug          		 Enable debug mode (same as --log-level=T)\n");
	fprintf(fp, "  -h, --help           		 Show this help and quit\n");
	fprintf(fp, "  -l, --log-level=LVL  		 Set the default log level to LVL (`N`, `T`, `D`,\n");
	fprintf(fp, "                       		 `I`, `W` (default), `E`, or `F`)\n");
	fprintf(fp, "      --omit-home-plugin-path       Omit home plugins from plugin search path\n");
	fprintf(fp, "                                    (~/.local/lib/babeltrace2/plugins)\n");
	fprintf(fp, "      --omit-system-plugin-path     Omit system plugins from plugin search path\n");
	fprintf(fp, "      --plugin-path=PATH[:PATH]...  Add PATH to the list of paths from which\n");
	fprintf(fp, "                                    dynamic plugins can be loaded\n");
	fprintf(fp, "  -v, --verbose        		 Enable verbose mode (same as --log-level=I)\n");
	fprintf(fp, "  -V, --version        		 Show version and quit\n");
	fprintf(fp, "\n");
	fprintf(fp, "Available commands:\n");
	fprintf(fp, "\n");
	fprintf(fp, "    convert       Convert and trim traces (default)\n");
	fprintf(fp, "    help          Get help for a plugin or a component class\n");
	fprintf(fp, "    list-plugins  List available plugins and their content\n");
	fprintf(fp, "    query         Query objects from a component class\n");
	fprintf(fp, "    run           Build a processing graph and run it\n");
	fprintf(fp, "\n");
	fprintf(fp, "Use `babeltrace2 COMMAND --help` to show the help of COMMAND.\n");
}

enum bt_config_cli_args_status bt_config_cli_args_create(int argc,
		const char *argv[], struct bt_config **cfg,
		bool omit_system_plugin_path,
		bool omit_home_plugin_path,
		const bt_value *initial_plugin_paths,
		const bt_interrupter *interrupter)
{
	enum bt_config_cli_args_status status;
	int top_level_argc;
	const char **top_level_argv;
	int command_argc = -1;
	const char **command_argv = NULL;
	const char *command_name = NULL;
	int default_log_level = -1;
	struct argpar_iter *argpar_iter = NULL;
	const struct argpar_item *argpar_item = NULL;
	const struct argpar_error *argpar_error = NULL;
	bt_value *plugin_paths = NULL;
	unsigned int consumed_args;

	/* Top-level option descriptions. */
	static const struct argpar_opt_descr descrs[] = {
		{ OPT_DEBUG, 'd', "debug", false },
		{ OPT_HELP, 'h', "help", false },
		{ OPT_LOG_LEVEL, 'l', "log-level", true },
		{ OPT_VERBOSE, 'v', "verbose", false },
		{ OPT_VERSION, 'V', "version", false},
		{ OPT_OMIT_HOME_PLUGIN_PATH, '\0', "omit-home-plugin-path", false },
		{ OPT_OMIT_SYSTEM_PLUGIN_PATH, '\0', "omit-system-plugin-path", false },
		{ OPT_PLUGIN_PATH, '\0', "plugin-path", true },
		ARGPAR_OPT_DESCR_SENTINEL
	};

	enum command_type {
		COMMAND_TYPE_NONE = -1,
		COMMAND_TYPE_RUN = 0,
		COMMAND_TYPE_CONVERT,
		COMMAND_TYPE_LIST_PLUGINS,
		COMMAND_TYPE_HELP,
		COMMAND_TYPE_QUERY,
	} command_type = COMMAND_TYPE_NONE;

	if (!initial_plugin_paths) {
		plugin_paths = bt_value_array_create();
		if (!plugin_paths) {
			goto error;
		}
	} else {
		bt_value_copy_status copy_status = bt_value_copy(
			initial_plugin_paths, &plugin_paths);
		if (copy_status) {
			goto error;
		}
	}

	BT_ASSERT(plugin_paths);

	/*
	 * The `BABELTRACE_PLUGIN_PATH` paths take precedence over the
	 * `--plugin-path` option's paths, so append it now before
	 * parsing the general options.
	 */
	if (append_env_var_plugin_paths(plugin_paths)) {
		goto error;
	}

	if (argc <= 1) {
		print_version();
		puts("");
		print_gen_usage(stdout);
		status = BT_CONFIG_CLI_ARGS_STATUS_INFO_ONLY;
		goto end;
	}

	/* Skip first argument, the name of the program. */
	top_level_argc = argc - 1;
	top_level_argv = argv + 1;

	argpar_iter = argpar_iter_create(top_level_argc, top_level_argv, descrs);
	if (!argpar_iter) {
		BT_CLI_LOGE_APPEND_CAUSE_OOM();
		goto error;
	}

	while (true) {
		enum argpar_iter_next_status argpar_status;

		ARGPAR_ITEM_DESTROY_AND_RESET(argpar_item);
		argpar_status = argpar_iter_next(argpar_iter, &argpar_item, &argpar_error);

		switch (argpar_status) {
		case ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY:
			BT_CLI_LOGE_APPEND_CAUSE_OOM();
			goto error;
		case ARGPAR_ITER_NEXT_STATUS_ERROR:
		{
			if (argpar_error_type(argpar_error)
					!= ARGPAR_ERROR_TYPE_UNKNOWN_OPT) {
				GString *err_str = format_arg_error(argpar_error, top_level_argv,
					0, "While parsing command-line arguments");
				BT_CLI_LOGE_APPEND_CAUSE("%s", err_str->str);
				g_string_free(err_str, TRUE);
				goto error;
			}

			break;
		}
		default:
			break;
		}

		if (argpar_status == ARGPAR_ITER_NEXT_STATUS_END) {
			break;
		}

		if (argpar_status == ARGPAR_ITER_NEXT_STATUS_ERROR) {
			BT_ASSERT(argpar_error_type(argpar_error) ==
				ARGPAR_ERROR_TYPE_UNKNOWN_OPT);
			/*
			 * Unknown option, assume this is implicitly the
			 * convert command, stop processing arguments.
			 */
			break;
		}

		if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) {
			const struct argpar_opt_descr *opt_descr =
				argpar_item_opt_descr(argpar_item);
			const char *arg = argpar_item_opt_arg(argpar_item);

			switch (opt_descr->id) {
			case OPT_DEBUG:
				default_log_level =
					logging_level_min(default_log_level, BT_LOG_TRACE);
				break;
			case OPT_VERBOSE:
				default_log_level =
					logging_level_min(default_log_level, BT_LOG_INFO);
				break;
			case OPT_LOG_LEVEL:
			{
				int level = bt_log_get_level_from_string(arg);

				if (level < 0) {
					BT_CLI_LOGE_APPEND_CAUSE(
						"Invalid argument for --log-level option:\n    %s",
						arg);
					goto error;
				}

				default_log_level =
					logging_level_min(default_log_level, level);
				break;
			}
			case OPT_PLUGIN_PATH:
				if (bt_config_append_plugin_paths_check_setuid_setgid(
						plugin_paths, arg)) {
					goto error;
				}
				break;
			case OPT_OMIT_SYSTEM_PLUGIN_PATH:
				omit_system_plugin_path = true;
				break;
			case OPT_OMIT_HOME_PLUGIN_PATH:
				omit_home_plugin_path = true;
				break;
			case OPT_VERSION:
				print_version();
				status = BT_CONFIG_CLI_ARGS_STATUS_INFO_ONLY;
				goto end;
			case OPT_HELP:
				print_gen_usage(stdout);
				status = BT_CONFIG_CLI_ARGS_STATUS_INFO_ONLY;
				goto end;
			default:
				bt_common_abort();
			}
		} else {
			const char *arg = argpar_item_non_opt_arg(argpar_item);
			unsigned int orig_index = argpar_item_non_opt_orig_index(argpar_item);

			/*
			 * First unknown argument: is it a known command
			 * name?
			 */
			command_argc = top_level_argc - orig_index - 1;
			command_argv = &top_level_argv[orig_index + 1];

			if (strcmp(arg, "convert") == 0) {
				command_type = COMMAND_TYPE_CONVERT;
				command_name = "convert";
			} else if (strcmp(arg, "list-plugins") == 0) {
				command_type = COMMAND_TYPE_LIST_PLUGINS;
				command_name = "list-plugins";
			} else if (strcmp(arg, "help") == 0) {
				command_type = COMMAND_TYPE_HELP;
				command_name = "help";
			} else if (strcmp(arg, "query") == 0) {
				command_type = COMMAND_TYPE_QUERY;
				command_name = "query";
			} else if (strcmp(arg, "run") == 0) {
				command_type = COMMAND_TYPE_RUN;
				command_name = "run";
			} else {
				/*
				 * Non-option argument, but not a known
				 * command name: assume the default
				 * `convert` command.
				 */
				command_type = COMMAND_TYPE_CONVERT;
				command_name = "convert";
				command_argc++;
				command_argv--;
			}

			/* Stop processing arguments. */
			break;
		}
	}

	if (command_type == COMMAND_TYPE_NONE) {
		unsigned int ingested_orig_args = argpar_iter_ingested_orig_args(argpar_iter);

		if (ingested_orig_args == top_level_argc) {
			/*
			 * We only got non-help, non-version general options
			 * like --verbose and --debug, without any other
			 * arguments, so we can't do anything useful: print the
			 * usage and quit.
			 */
			print_gen_usage(stdout);
			status = BT_CONFIG_CLI_ARGS_STATUS_INFO_ONLY;
			goto end;
		}

		/*
		 * We stopped on an unknown option argument (and therefore
		 * didn't see a command name). Assume `convert` command.
		 */
		command_type = COMMAND_TYPE_CONVERT;
		command_name = "convert";
		command_argc = top_level_argc - ingested_orig_args;
		command_argv = &top_level_argv[ingested_orig_args];
	}

	BT_ASSERT(command_argv);
	BT_ASSERT(command_argc >= 0);

	/*
	 * For all commands other than `convert`, we now know the log level to
	 * use, so we can apply it with `set_auto_log_levels`.
	 *
	 * The convert command has `--debug` and `--verbose` arguments that are
	 * equivalent to the top-level arguments of the same name.  So after it
	 * has parsed its arguments, `bt_config_convert_from_args` calls
	 * `set_auto_log_levels` itself.
	 */
	if (command_type != COMMAND_TYPE_CONVERT) {
		set_auto_log_levels(&default_log_level);
	}

	/*
	 * At this point, `plugin_paths` contains the initial plugin
	 * paths, the paths from the `BABELTRACE_PLUGIN_PATH` paths, and
	 * the paths from the `--plugin-path` option.
	 *
	 * Now append the user and system plugin paths.
	 */
	if (append_home_and_system_plugin_paths(plugin_paths,
			omit_system_plugin_path, omit_home_plugin_path)) {
		goto error;
	}

	consumed_args = argpar_iter_ingested_orig_args(argpar_iter);

	switch (command_type) {
	case COMMAND_TYPE_RUN:
		status = bt_config_run_from_args(command_argc, command_argv,
			cfg, plugin_paths,
			default_log_level, consumed_args);
		break;
	case COMMAND_TYPE_CONVERT:
		status = bt_config_convert_from_args(command_argc, command_argv,
			cfg, plugin_paths, &default_log_level, interrupter,
			consumed_args);
		break;
	case COMMAND_TYPE_LIST_PLUGINS:
		status = bt_config_list_plugins_from_args(command_argc,
			command_argv, cfg, plugin_paths, consumed_args);
		break;
	case COMMAND_TYPE_HELP:
		status = bt_config_help_from_args(command_argc,
			command_argv, cfg, plugin_paths,
			default_log_level, consumed_args);
		break;
	case COMMAND_TYPE_QUERY:
		status = bt_config_query_from_args(command_argc,
			command_argv, cfg, plugin_paths,
			default_log_level, consumed_args);
		break;
	default:
		bt_common_abort();
	}

	if (status == BT_CONFIG_CLI_ARGS_STATUS_ERROR) {
		goto error;
	} else if (status == BT_CONFIG_CLI_ARGS_STATUS_INFO_ONLY) {
		goto end;
	}

	BT_ASSERT(status == BT_CONFIG_CLI_ARGS_STATUS_OK);
	BT_ASSERT(*cfg);
	BT_ASSERT(default_log_level >= BT_LOG_TRACE);
	(*cfg)->log_level = default_log_level;
	(*cfg)->command_name = command_name;

	status = BT_CONFIG_CLI_ARGS_STATUS_OK;
	goto end;

error:
	status = BT_CONFIG_CLI_ARGS_STATUS_ERROR;

end:
	argpar_error_destroy(argpar_error);
	argpar_item_destroy(argpar_item);
	argpar_iter_destroy(argpar_iter);
	bt_value_put_ref(plugin_paths);
	return status;
}
